View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  
13  package com.eviware.soapui.tools;
14  
15  import java.io.File;
16  import java.io.FileOutputStream;
17  import java.io.PrintWriter;
18  import java.io.StringWriter;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.cli.CommandLine;
26  
27  import com.eviware.soapui.SoapUI;
28  import com.eviware.soapui.impl.wsdl.WsdlProject;
29  import com.eviware.soapui.impl.wsdl.WsdlTestSuite;
30  import com.eviware.soapui.impl.wsdl.testcase.WsdlProjectRunner;
31  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
32  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner;
33  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestSuiteRunner;
34  import com.eviware.soapui.impl.wsdl.teststeps.WsdlRunTestCaseTestStep;
35  import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
36  import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStepResult;
37  import com.eviware.soapui.model.iface.Attachment;
38  import com.eviware.soapui.model.iface.MessageExchange;
39  import com.eviware.soapui.model.project.ProjectFactoryRegistry;
40  import com.eviware.soapui.model.support.ModelSupport;
41  import com.eviware.soapui.model.support.ProjectRunListenerAdapter;
42  import com.eviware.soapui.model.testsuite.Assertable;
43  import com.eviware.soapui.model.testsuite.AssertionError;
44  import com.eviware.soapui.model.testsuite.ProjectRunContext;
45  import com.eviware.soapui.model.testsuite.ProjectRunner;
46  import com.eviware.soapui.model.testsuite.TestAssertion;
47  import com.eviware.soapui.model.testsuite.TestCase;
48  import com.eviware.soapui.model.testsuite.TestCaseRunContext;
49  import com.eviware.soapui.model.testsuite.TestCaseRunner;
50  import com.eviware.soapui.model.testsuite.TestStep;
51  import com.eviware.soapui.model.testsuite.TestStepResult;
52  import com.eviware.soapui.model.testsuite.TestSuite;
53  import com.eviware.soapui.model.testsuite.TestSuiteRunner;
54  import com.eviware.soapui.model.testsuite.Assertable.AssertionStatus;
55  import com.eviware.soapui.model.testsuite.TestRunner.Status;
56  import com.eviware.soapui.model.testsuite.TestStepResult.TestStepStatus;
57  import com.eviware.soapui.report.JUnitReportCollector;
58  import com.eviware.soapui.support.StringUtils;
59  import com.eviware.soapui.support.Tools;
60  import com.eviware.soapui.support.types.StringToObjectMap;
61  
62  /***
63   * Standalone test-runner used from maven-plugin, can also be used from
64   * command-line (see xdocs) or directly from other classes.
65   * <p>
66   * For standalone usage, set the project file (with setProjectFile) and other
67   * desired properties before calling run
68   * </p>
69   * 
70   * @author Ole.Matzura
71   */
72  
73  public class SoapUITestCaseRunner extends AbstractSoapUITestRunner
74  {
75  	public static final String SOAPUI_EXPORT_SEPARATOR = "soapui.export.separator";
76  
77  	public static final String TITLE = "soapUI " + SoapUI.SOAPUI_VERSION + " TestCase Runner";
78  
79  	private String testSuite;
80  	private String testCase;
81  	private List<TestAssertion> assertions = new ArrayList<TestAssertion>();
82  	private Map<TestAssertion, WsdlTestStepResult> assertionResults = new HashMap<TestAssertion, WsdlTestStepResult>();
83  	// private List<TestCaseRunner> runningTests = new
84  	// ArrayList<TestCaseRunner>();
85  	private List<TestCase> failedTests = new ArrayList<TestCase>();
86  
87  	private int testSuiteCount;
88  	private int testCaseCount;
89  	private int testStepCount;
90  	private int testAssertionCount;
91  
92  	private boolean printReport;
93  	private boolean exportAll;
94  	private boolean ignoreErrors;
95  	private boolean junitReport;
96  	private int exportCount;
97  	private int maxErrors = 5;
98  	private JUnitReportCollector reportCollector;
99  	// private WsdlProject project;
100 	private String projectPassword;
101 	private boolean saveAfterRun;
102 
103 	/***
104 	 * Runs the tests in the specified soapUI project file, see soapUI xdocs for
105 	 * details.
106 	 * 
107 	 * @param args
108 	 * @throws Exception
109 	 */
110 
111 	public static void main( String[] args ) throws Exception
112 	{
113 		System.exit( new SoapUITestCaseRunner().runFromCommandLine( args ) );
114 	}
115 
116 	protected boolean processCommandLine( CommandLine cmd )
117 	{
118 		String message = "";
119 		if( cmd.hasOption( "e" ) )
120 			setEndpoint( cmd.getOptionValue( "e" ) );
121 
122 		if( cmd.hasOption( "s" ) )
123 		{
124 			String testSuite = getCommandLineOptionSubstSpace( cmd, "s" );
125 			setTestSuite( testSuite );
126 		}
127 
128 		if( cmd.hasOption( "c" ) )
129 		{
130 			String testCase = getCommandLineOptionSubstSpace( cmd, "c" );
131 			setTestCase( testCase );
132 		}
133 
134 		if( cmd.hasOption( "u" ) )
135 			setUsername( cmd.getOptionValue( "u" ) );
136 
137 		if( cmd.hasOption( "p" ) )
138 			setPassword( cmd.getOptionValue( "p" ) );
139 
140 		if( cmd.hasOption( "w" ) )
141 			setWssPasswordType( cmd.getOptionValue( "w" ) );
142 
143 		if( cmd.hasOption( "d" ) )
144 			setDomain( cmd.getOptionValue( "d" ) );
145 
146 		if( cmd.hasOption( "h" ) )
147 			setHost( cmd.getOptionValue( "h" ) );
148 
149 		if( cmd.hasOption( "f" ) )
150 			setOutputFolder( getCommandLineOptionSubstSpace( cmd, "f" ) );
151 
152 		if( cmd.hasOption( "t" ) )
153 			setSettingsFile( getCommandLineOptionSubstSpace( cmd, "t" ) );
154 
155 		if( cmd.hasOption( "x" ) )
156 		{
157 			setProjectPassword( cmd.getOptionValue( "x" ) );
158 		}
159 
160 		if( cmd.hasOption( "v" ) )
161 		{
162 			setSoapUISettingsPassword( cmd.getOptionValue( "v" ) );
163 		}
164 
165 		if( cmd.hasOption( "D" ) )
166 		{
167 			setSystemProperties( cmd.getOptionValues( "D" ) );
168 		}
169 
170 		if( cmd.hasOption( "G" ) )
171 		{
172 			setGlobalProperties( cmd.getOptionValues( "G" ) );
173 		}
174 
175 		if( cmd.hasOption( "P" ) )
176 		{
177 			setProjectProperties( cmd.getOptionValues( "P" ) );
178 		}
179 
180 		setIgnoreError( cmd.hasOption( "I" ) );
181 		setEnableUI( cmd.hasOption( "i" ) );
182 		setPrintReport( cmd.hasOption( "r" ) );
183 		setExportAll( cmd.hasOption( "a" ) );
184 		if( cmd.hasOption( "A" ) )
185 		{
186 			setExportAll( true );
187 			System.setProperty( SOAPUI_EXPORT_SEPARATOR, File.separator );
188 		}
189 
190 		setJUnitReport( cmd.hasOption( "j" ) );
191 
192 		if( cmd.hasOption( "m" ) )
193 			setMaxErrors( Integer.parseInt( cmd.getOptionValue( "m" ) ) );
194 
195 		setSaveAfterRun( cmd.hasOption( "S" ) );
196 
197 		if( message.length() > 0 )
198 		{
199 			log.error( message );
200 			return false;
201 		}
202 
203 		return true;
204 	}
205 
206 	public void setMaxErrors( int maxErrors )
207 	{
208 		this.maxErrors = maxErrors;
209 	}
210 
211 	protected int getMaxErrors()
212 	{
213 		return maxErrors;
214 	}
215 
216 	public void setSaveAfterRun( boolean saveAfterRun )
217 	{
218 		this.saveAfterRun = saveAfterRun;
219 	}
220 
221 	public void setProjectPassword( String projectPassword )
222 	{
223 		this.projectPassword = projectPassword;
224 	}
225 
226 	public String getProjectPassword()
227 	{
228 		return projectPassword;
229 	}
230 
231 	protected SoapUIOptions initCommandLineOptions()
232 	{
233 		SoapUIOptions options = new SoapUIOptions( "testrunner" );
234 		options.addOption( "e", true, "Sets the endpoint" );
235 		options.addOption( "s", true, "Sets the testsuite" );
236 		options.addOption( "c", true, "Sets the testcase" );
237 		options.addOption( "u", true, "Sets the username" );
238 		options.addOption( "p", true, "Sets the password" );
239 		options.addOption( "w", true, "Sets the WSS password type, either 'Text' or 'Digest'" );
240 		options.addOption( "i", false, "Enables Swing UI for scripts" );
241 		options.addOption( "d", true, "Sets the domain" );
242 		options.addOption( "h", true, "Sets the host" );
243 		options.addOption( "r", false, "Prints a small summary report" );
244 		options.addOption( "f", true, "Sets the output folder to export results to" );
245 		options.addOption( "j", false, "Sets the output to include JUnit XML reports" );
246 		options.addOption( "m", false, "Sets the maximum number of TestStep errors to save for each testcase" );
247 		options.addOption( "a", false, "Turns on exporting of all results" );
248 		options.addOption( "A", false, "Turns on exporting of all results using folders instead of long filenames" );
249 		options.addOption( "t", true, "Sets the soapui-settings.xml file to use" );
250 		options.addOption( "x", true, "Sets project password for decryption if project is encrypted" );
251 		options.addOption( "v", true, "Sets password for soapui-settings.xml file" );
252 		options.addOption( "D", true, "Sets system property with name=value" );
253 		options.addOption( "G", true, "Sets global property with name=value" );
254 		options.addOption( "P", true, "Sets or overrides project property with name=value" );
255 		options.addOption( "I", false, "Do not stop if error occurs, ignore them" );
256 		options.addOption( "S", false, "Saves the project after running the tests" );
257 
258 		return options;
259 	}
260 
261 	/***
262 	 * Add console appender to groovy log
263 	 */
264 
265 	public void setExportAll( boolean exportAll )
266 	{
267 		this.exportAll = exportAll;
268 	}
269 
270 	public void setJUnitReport( boolean junitReport )
271 	{
272 		this.junitReport = junitReport;
273 		if( junitReport )
274 			reportCollector = createJUnitReportCollector();
275 	}
276 
277 	protected JUnitReportCollector createJUnitReportCollector()
278 	{
279 		return new JUnitReportCollector( maxErrors );
280 	}
281 
282 	public SoapUITestCaseRunner()
283 	{
284 		super( SoapUITestCaseRunner.TITLE );
285 	}
286 
287 	public SoapUITestCaseRunner( String title )
288 	{
289 		super( title );
290 	}
291 
292 	/***
293 	 * Controls if a short test summary should be printed after the test runs
294 	 * 
295 	 * @param printReport
296 	 *           a flag controlling if a summary should be printed
297 	 */
298 
299 	public void setPrintReport( boolean printReport )
300 	{
301 		this.printReport = printReport;
302 	}
303 
304 	public void setIgnoreError( boolean ignoreErrors )
305 	{
306 		this.ignoreErrors = ignoreErrors;
307 	}
308 
309 	public boolean runRunner() throws Exception
310 	{
311 		initGroovyLog();
312 
313 		assertions.clear();
314 
315 		String projectFile = getProjectFile();
316 
317 		WsdlProject project = ( WsdlProject )ProjectFactoryRegistry.getProjectFactory( "wsdl" ).createNew( projectFile,
318 				getProjectPassword() );
319 
320 		if( project.isDisabled() )
321 			throw new Exception( "Failed to load soapUI project file [" + projectFile + "]" );
322 
323 		initProject( project );
324 		ensureOutputFolder( project );
325 
326 		log.info( "Running soapUI tests in project [" + project.getName() + "]" );
327 
328 		long startTime = System.nanoTime();
329 
330 		List<TestCase> testCasesToRun = new ArrayList<TestCase>();
331 
332 		// validate testSuite argument
333 		if( testSuite != null && project.getTestSuiteByName( testSuite ) == null )
334 			throw new Exception( "TestSuite with name [" + testSuite + "] is missing in Project [" + project.getName()
335 					+ "]" );
336 
337 		// start by listening to all testcases.. (since one testcase can call
338 		// another)
339 		for( int c = 0; c < project.getTestSuiteCount(); c++ )
340 		{
341 			TestSuite suite = project.getTestSuiteAt( c );
342 			for( int i = 0; i < suite.getTestCaseCount(); i++ )
343 			{
344 				TestCase tc = suite.getTestCaseAt( i );
345 				if( ( testSuite == null || suite.getName().equals( suite.getName() ) ) && testCase != null
346 						&& tc.getName().equals( testCase ) )
347 					testCasesToRun.add( tc );
348 
349 				addListeners( tc );
350 			}
351 		}
352 
353 		try
354 		{
355 			// validate testSuite argument
356 			if( testCase != null && testCasesToRun.size() == 0 )
357 			{
358 				if( testSuite == null )
359 					throw new Exception( "TestCase with name [" + testCase + "] is missing in Project [" + project.getName()
360 							+ "]" );
361 				else
362 					throw new Exception( "TestCase with name [" + testCase + "] in TestSuite [" + testSuite
363 							+ "] is missing in Project [" + project.getName() + "]" );
364 			}
365 
366 			// decide what to run
367 			if( testCasesToRun.size() > 0 )
368 			{
369 				for( TestCase testCase : testCasesToRun )
370 					runTestCase( ( WsdlTestCase )testCase );
371 			}
372 			else if( testSuite != null )
373 			{
374 				WsdlTestSuite ts = project.getTestSuiteByName( testSuite );
375 				if( ts == null )
376 					throw new Exception( "TestSuite with name [" + testSuite + "] not found in project" );
377 				else
378 					runSuite( ts );
379 			}
380 			else
381 			{
382 				runProject( project );
383 			}
384 
385 			long timeTaken = ( System.nanoTime() - startTime ) / 1000000;
386 
387 			if( printReport )
388 			{
389 				printReport( timeTaken );
390 			}
391 
392 			exportReports( project );
393 
394 			if( saveAfterRun && !project.isRemote() )
395 			{
396 				try
397 				{
398 					project.save();
399 				}
400 				catch( Throwable t )
401 				{
402 					log.error( "Failed to save project", t );
403 				}
404 			}
405 
406 			if( ( assertions.size() > 0 || failedTests.size() > 0 ) && !ignoreErrors )
407 			{
408 				throwFailureException();
409 			}
410 
411 			return true;
412 		}
413 		finally
414 		{
415 			for( int c = 0; c < project.getTestSuiteCount(); c++ )
416 			{
417 				TestSuite suite = project.getTestSuiteAt( c );
418 				for( int i = 0; i < suite.getTestCaseCount(); i++ )
419 				{
420 					TestCase tc = suite.getTestCaseAt( i );
421 					removeListeners( tc );
422 				}
423 			}
424 		}
425 	}
426 
427 	protected void removeListeners( TestCase tc )
428 	{
429 		tc.removeTestRunListener( this );
430 		if( junitReport )
431 			tc.removeTestRunListener( reportCollector );
432 	}
433 
434 	protected void runProject( WsdlProject project )
435 	{
436 		// add listener for counting..
437 		InternalProjectRunListener projectRunListener = new InternalProjectRunListener();
438 		project.addProjectRunListener( projectRunListener );
439 
440 		try
441 		{
442 			log.info( ( "Running Project [" + project.getName() + "], runType = " + project.getRunType() ) );
443 			WsdlProjectRunner runner = project.run( new StringToObjectMap(), false );
444 			log.info( "Project [" + project.getName() + "] finished with status [" + runner.getStatus() + "] in "
445 					+ runner.getTimeTaken() + "ms" );
446 		}
447 		catch( Exception e )
448 		{
449 			e.printStackTrace();
450 		}
451 		finally
452 		{
453 			project.removeProjectRunListener( projectRunListener );
454 		}
455 	}
456 
457 	protected void initProject( WsdlProject project ) throws Exception
458 	{
459 		initProjectProperties( project );
460 	}
461 
462 	protected void exportReports( WsdlProject project ) throws Exception
463 	{
464 		if( junitReport )
465 		{
466 			exportJUnitReports( reportCollector, getAbsoluteOutputFolder( project ), project );
467 		}
468 	}
469 
470 	protected void addListeners( TestCase tc )
471 	{
472 		tc.addTestRunListener( this );
473 		if( junitReport )
474 			tc.addTestRunListener( reportCollector );
475 	}
476 
477 	protected void throwFailureException() throws Exception
478 	{
479 		StringBuffer buf = new StringBuffer();
480 
481 		for( int c = 0; c < assertions.size(); c++ )
482 		{
483 			TestAssertion assertion = assertions.get( c );
484 			Assertable assertable = assertion.getAssertable();
485 			if( assertable instanceof WsdlTestStep )
486 				failedTests.remove( ( ( WsdlTestStep )assertable ).getTestCase() );
487 
488 			buf.append( assertion.getName() + " in [" + assertable.getModelItem().getName() + "] failed;\n" );
489 			buf.append( Arrays.toString( assertion.getErrors() ) + "\n" );
490 
491 			WsdlTestStepResult result = assertionResults.get( assertion );
492 			StringWriter stringWriter = new StringWriter();
493 			PrintWriter writer = new PrintWriter( stringWriter );
494 			result.writeTo( writer );
495 			buf.append( stringWriter.toString() );
496 		}
497 
498 		while( !failedTests.isEmpty() )
499 		{
500 			buf.append( "TestCase [" + failedTests.remove( 0 ).getName() + "] failed without assertions\n" );
501 		}
502 
503 		throw new Exception( buf.toString() );
504 	}
505 
506 	public void exportJUnitReports( JUnitReportCollector collector, String folder, WsdlProject project )
507 			throws Exception
508 	{
509 		collector.saveReports( folder == null ? "" : folder );
510 	}
511 
512 	public void printReport( long timeTaken )
513 	{
514 		System.out.println();
515 		System.out.println( "SoapUI " + SoapUI.SOAPUI_VERSION + " TestCaseRunner Summary" );
516 		System.out.println( "-----------------------------" );
517 		System.out.println( "Time Taken: " + timeTaken + "ms" );
518 		System.out.println( "Total TestSuites: " + testSuiteCount );
519 		System.out.println( "Total TestCases: " + testCaseCount + " (" + failedTests.size() + " failed)" );
520 		System.out.println( "Total TestSteps: " + testStepCount );
521 		System.out.println( "Total Request Assertions: " + testAssertionCount );
522 		System.out.println( "Total Failed Assertions: " + assertions.size() );
523 		System.out.println( "Total Exported Results: " + exportCount );
524 	}
525 
526 	/***
527 	 * Run tests in the specified TestSuite
528 	 * 
529 	 * @param suite
530 	 *           the TestSuite to run
531 	 */
532 
533 	protected void runSuite( WsdlTestSuite suite )
534 	{
535 		try
536 		{
537 			log.info( ( "Running TestSuite [" + suite.getName() + "], runType = " + suite.getRunType() ) );
538 			WsdlTestSuiteRunner runner = suite.run( new StringToObjectMap(), false );
539 			log.info( "TestSuite [" + suite.getName() + "] finished with status [" + runner.getStatus() + "] in "
540 					+ ( runner.getTimeTaken() ) + "ms" );
541 		}
542 		catch( Exception e )
543 		{
544 			e.printStackTrace();
545 		}
546 		finally
547 		{
548 			testSuiteCount++ ;
549 		}
550 	}
551 
552 	/***
553 	 * Runs the specified TestCase
554 	 * 
555 	 * @param testCase
556 	 *           the testcase to run
557 	 * @param context
558 	 */
559 
560 	protected void runTestCase( WsdlTestCase testCase )
561 	{
562 		try
563 		{
564 			log.info( "Running TestCase [" + testCase.getName() + "]" );
565 			WsdlTestCaseRunner runner = testCase.run( new StringToObjectMap(), false );
566 			log.info( "TestCase [" + testCase.getName() + "] finished with status [" + runner.getStatus() + "] in "
567 					+ ( runner.getTimeTaken() ) + "ms" );
568 		}
569 		catch( Exception e )
570 		{
571 			e.printStackTrace();
572 		}
573 	}
574 
575 	/***
576 	 * Sets the testcase to run
577 	 * 
578 	 * @param testCase
579 	 *           the testcase to run
580 	 */
581 
582 	public void setTestCase( String testCase )
583 	{
584 		this.testCase = testCase;
585 	}
586 
587 	/***
588 	 * Sets the TestSuite to run. If not set all TestSuites in the specified
589 	 * project file are run
590 	 * 
591 	 * @param testSuite
592 	 *           the testSuite to run.
593 	 */
594 
595 	public void setTestSuite( String testSuite )
596 	{
597 		this.testSuite = testSuite;
598 	}
599 
600 	public void beforeRun( TestCaseRunner testRunner, TestCaseRunContext runContext )
601 	{
602 		log.info( "Running soapUI testcase [" + testRunner.getTestCase().getName() + "]" );
603 	}
604 
605 	public void beforeStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStep currentStep )
606 	{
607 		super.beforeStep( testRunner, runContext, currentStep );
608 
609 		if( currentStep != null )
610 			log.info( "running step [" + currentStep.getName() + "]" );
611 	}
612 
613 	public void afterStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStepResult result )
614 	{
615 		super.afterStep( testRunner, runContext, result );
616 		TestStep currentStep = runContext.getCurrentStep();
617 
618 		if( currentStep instanceof Assertable )
619 		{
620 			Assertable requestStep = ( Assertable )currentStep;
621 			for( int c = 0; c < requestStep.getAssertionCount(); c++ )
622 			{
623 				TestAssertion assertion = requestStep.getAssertionAt( c );
624 				log.info( "Assertion [" + assertion.getName() + "] has status " + assertion.getStatus() );
625 				if( assertion.getStatus() == AssertionStatus.FAILED )
626 				{
627 					for( AssertionError error : assertion.getErrors() )
628 						log.error( "ASSERTION FAILED -> " + error.getMessage() );
629 
630 					assertions.add( assertion );
631 					assertionResults.put( assertion, ( WsdlTestStepResult )result );
632 				}
633 
634 				testAssertionCount++ ;
635 			}
636 		}
637 
638 		String countPropertyName = currentStep.getName() + " run count";
639 		Long count = ( Long )runContext.getProperty( countPropertyName );
640 		if( count == null )
641 		{
642 			count = new Long( 0 );
643 		}
644 
645 		runContext.setProperty( countPropertyName, new Long( count.longValue() + 1 ) );
646 
647 		if( result.getStatus() == TestStepStatus.FAILED || exportAll )
648 		{
649 			try
650 			{
651 				String exportSeparator = System.getProperty( SOAPUI_EXPORT_SEPARATOR, "-" );
652 
653 				TestCase tc = currentStep.getTestCase();
654 				String nameBase = StringUtils.createFileName( tc.getTestSuite().getName(), '_' ) + exportSeparator
655 						+ StringUtils.createFileName( tc.getName(), '_' ) + exportSeparator
656 						+ StringUtils.createFileName( currentStep.getName(), '_' ) + "-" + count.longValue() + "-"
657 						+ result.getStatus();
658 
659 				WsdlTestCaseRunner callingTestCaseRunner = ( WsdlTestCaseRunner )runContext
660 						.getProperty( "#CallingTestCaseRunner#" );
661 
662 				if( callingTestCaseRunner != null )
663 				{
664 					WsdlTestCase ctc = callingTestCaseRunner.getTestCase();
665 					WsdlRunTestCaseTestStep runTestCaseTestStep = ( WsdlRunTestCaseTestStep )runContext
666 							.getProperty( "#CallingRunTestCaseStep#" );
667 
668 					nameBase = StringUtils.createFileName( ctc.getTestSuite().getName(), '_' ) + exportSeparator
669 							+ StringUtils.createFileName( ctc.getName(), '_' ) + exportSeparator
670 							+ StringUtils.createFileName( runTestCaseTestStep.getName(), '_' ) + exportSeparator
671 							+ StringUtils.createFileName( tc.getTestSuite().getName(), '_' ) + exportSeparator
672 							+ StringUtils.createFileName( tc.getName(), '_' ) + exportSeparator
673 							+ StringUtils.createFileName( currentStep.getName(), '_' ) + "-" + count.longValue() + "-"
674 							+ result.getStatus();
675 				}
676 
677 				String absoluteOutputFolder = getAbsoluteOutputFolder( ModelSupport.getModelItemProject( tc ) );
678 				String fileName = absoluteOutputFolder + File.separator + nameBase + ".txt";
679 
680 				if( result.getStatus() == TestStepStatus.FAILED )
681 					log.error( currentStep.getName() + " failed, exporting to [" + fileName + "]" );
682 
683 				new File( fileName ).getParentFile().mkdirs();
684 
685 				PrintWriter writer = new PrintWriter( fileName );
686 				result.writeTo( writer );
687 				writer.close();
688 
689 				// write attachments
690 				if( result instanceof MessageExchange )
691 				{
692 					Attachment[] attachments = ( ( MessageExchange )result ).getResponseAttachments();
693 					if( attachments != null && attachments.length > 0 )
694 					{
695 						for( int c = 0; c < attachments.length; c++ )
696 						{
697 							fileName = nameBase + "-attachment-" + ( c + 1 ) + ".";
698 
699 							Attachment attachment = attachments[c];
700 							String contentType = attachment.getContentType();
701 							if( !"application/octet-stream".equals( contentType ) && contentType != null
702 									&& contentType.indexOf( '/' ) != -1 )
703 							{
704 								fileName += contentType.substring( contentType.lastIndexOf( '/' ) + 1 );
705 							}
706 							else
707 							{
708 								fileName += "dat";
709 							}
710 
711 							fileName = absoluteOutputFolder + File.separator + fileName;
712 
713 							FileOutputStream outFile = new FileOutputStream( fileName );
714 							Tools.writeAll( outFile, attachment.getInputStream() );
715 							outFile.close();
716 						}
717 					}
718 				}
719 
720 				exportCount++ ;
721 			}
722 			catch( Exception e )
723 			{
724 				log.error( "Error saving failed result: " + e, e );
725 			}
726 		}
727 
728 		testStepCount++ ;
729 	}
730 
731 	public void afterRun( TestCaseRunner testRunner, TestCaseRunContext runContext )
732 	{
733 		log.info( "Finished running soapUI testcase [" + testRunner.getTestCase().getName() + "], time taken: "
734 				+ testRunner.getTimeTaken() + "ms, status: " + testRunner.getStatus() );
735 
736 		if( testRunner.getStatus() == Status.FAILED )
737 		{
738 			failedTests.add( testRunner.getTestCase() );
739 		}
740 
741 		testCaseCount++ ;
742 	}
743 
744 	private class InternalProjectRunListener extends ProjectRunListenerAdapter
745 	{
746 		public void afterTestSuite( ProjectRunner projectRunner, ProjectRunContext runContext, TestSuiteRunner testRunner )
747 		{
748 			testSuiteCount++ ;
749 		}
750 	}
751 }