1 package net.avcompris.base.testutil.processes;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static com.google.common.collect.Iterables.toArray;
5 import static org.apache.commons.lang3.CharEncoding.UTF_8;
6 import static org.junit.Assert.assertTrue;
7
8 import java.io.File;
9 import java.io.FileOutputStream;
10 import java.io.IOException;
11 import java.io.OutputStream;
12 import java.io.OutputStreamWriter;
13 import java.io.PrintWriter;
14 import java.lang.reflect.Constructor;
15 import java.lang.reflect.GenericArrayType;
16 import java.lang.reflect.InvocationTargetException;
17 import java.lang.reflect.Method;
18 import java.lang.reflect.ParameterizedType;
19 import java.lang.reflect.Type;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.TreeSet;
27
28 import org.apache.commons.io.FileUtils;
29 import org.joda.time.DateTime;
30 import org.junit.Before;
31 import org.junit.Ignore;
32 import org.junit.Test;
33
34
35
36
37
38
39
40
41 public abstract class TestsWithProcessesBefore {
42
43 private final Method[] testMethods;
44
45 protected TestsWithProcessesBefore() {
46
47 final Class<? extends TestsWithProcessesBefore> thisClass = getClass();
48
49 final RequiresProcesses processBefore = thisClass.getAnnotation(RequiresProcesses.class);
50
51 if (processBefore == null) {
52
53 throw new RuntimeException("Test class should have annotation @ProcessBefore: " + thisClass.getName());
54 }
55
56 final Class<?>[] processClasses = processBefore.value();
57
58 for (final Class<?> processClass : processClasses) {
59
60 if (!AbstractProcess.class.isAssignableFrom(processClass)) {
61
62 throw new IllegalArgumentException("Declared process class should extend AbstractProcess: "
63 + processClass.getName());
64 }
65
66 @SuppressWarnings("unchecked")
67 final Class<? extends AbstractProcess<?, ?>> typedClass = (Class<? extends AbstractProcess<?, ?>>) processClass;
68
69 this.processClasses.add(typedClass);
70 }
71
72 final List<Method> methods = new ArrayList<Method>();
73
74 for (final Method method : thisClass.getMethods()) {
75
76 if (method.isAnnotationPresent(Test.class) && !method.isAnnotationPresent(Ignore.class)) {
77
78 final Class<?>[] paramTypes = method.getParameterTypes();
79
80 if (paramTypes == null || paramTypes.length == 0) {
81
82 methods.add(method);
83 }
84 }
85 }
86
87 testMethods = toArray(methods, Method.class);
88
89 storeInstance();
90 }
91
92 private void storeInstance() {
93
94 final TestsWithProcessesBefore cachedInstance = instances.get(getClass());
95
96 if (cachedInstance == null) {
97
98 try {
99
100 createInstance(this);
101
102 } catch (final IllegalAccessException e) {
103 throw new RuntimeException(e);
104 } catch (final InstantiationException e) {
105 throw new RuntimeException(e);
106 }
107 }
108 }
109
110 private static synchronized void createInstance(final TestsWithProcessesBefore instance)
111 throws InstantiationException, IllegalAccessException {
112
113 final Class<? extends TestsWithProcessesBefore> instanceClass = instance.getClass();
114
115 final TestsWithProcessesBefore cachedInstance = instances.get(instanceClass);
116
117 if (cachedInstance != null) {
118
119 return;
120 }
121
122 instances.put(instanceClass, instance);
123
124 try {
125
126 instance.initProcess();
127
128 } catch (final Throwable e) {
129
130 e.printStackTrace();
131
132 instance.releaseProcess();
133 }
134 }
135
136 private void initProcess() throws Exception {
137
138 for (final Class<? extends AbstractProcess<?, ?>> processClass : processClasses) {
139
140 final String processName = processClass.getSimpleName();
141
142 System.out.println("Creating process instance: " + processName + "...");
143
144 final AbstractProcess<?, ?> process = processClass.newInstance();
145
146 System.out.println("Initializing process: " + processName + "...");
147
148 process.setTests(this);
149
150 process.init();
151
152 System.out.println("Process " + processName + " initialized.");
153
154 final ProcessInstance processInstance = new ProcessInstance(process);
155
156 processInstances.put(processClass, processInstance);
157
158 for (final Method testMethod : testMethods) {
159
160 final String methodName = testMethod.getName();
161
162 processInstance.methodErrors.put(methodName, new ArrayList<Throwable>());
163
164 processInstance.methodFailures.put(methodName, new ArrayList<AssertionError>());
165
166 processInstance.processEntryResults.put(methodName, new ArrayList<ProcessEntryResult>());
167 }
168 }
169 }
170
171 private synchronized void releaseProcess() {
172
173 final List<Class<? extends AbstractProcess<?, ?>>> processClassesCopy = new ArrayList<Class<? extends AbstractProcess<?, ?>>>(
174 processClasses);
175
176 for (final Class<? extends AbstractProcess<?, ?>> processClass : processClassesCopy) {
177
178 final ProcessInstance processInstance = processInstances.get(processClass);
179
180 if (processInstance == null) {
181
182 continue;
183 }
184
185 final AbstractProcess<?, ?> process = processInstance.process;
186
187 final String processName = processClass.getSimpleName();
188
189 System.out.println("Releasing process: " + processName + "...");
190
191 boolean released = true;
192
193 try {
194
195 process.release();
196
197 } catch (final Throwable e) {
198
199 if (processInstance.errors != null) {
200
201 processInstance.errors.add(e);
202 }
203
204 e.printStackTrace();
205
206 released = false;
207
208 System.out.println("ERROR. While releasing process " + processName + ".");
209 }
210
211 if (released) {
212
213 System.out.println("Process " + processName + " released.");
214
215 processClasses.remove(processClass);
216 }
217 }
218 }
219
220 private final List<Class<? extends AbstractProcess<?, ?>>> processClasses = new ArrayList<Class<? extends AbstractProcess<?, ?>>>();
221
222 private static Map<Class<? extends TestsWithProcessesBefore>, TestsWithProcessesBefore> instances = new HashMap<Class<? extends TestsWithProcessesBefore>, TestsWithProcessesBefore>();
223
224 private final Map<Class<? extends AbstractProcess<?, ?>>, ProcessInstance> processInstances = new HashMap<Class<? extends AbstractProcess<?, ?>>, ProcessInstance>();
225
226 protected final boolean isProcessRunning() {
227
228 return running;
229 }
230
231 private static ParameterizedType getParameterizedTypeSuperclass(final Class<? extends AbstractProcess<?, ?>> c) {
232
233 for (Type k = c.getGenericSuperclass(); k != null;) {
234
235 if (ParameterizedType.class.isInstance(k)) {
236
237 return (ParameterizedType) k;
238 }
239
240 k = ((Class<?>) k).getGenericSuperclass();
241 }
242
243 throw new RuntimeException("Not implemented.");
244 }
245
246 protected final <X> X getProcessCurrentOfType(final Class<X> currentClass) throws Exception {
247
248 checkNotNull(currentClass, "currentClass");
249
250 for (final Class<? extends AbstractProcess<?, ?>> c : getProcessInstances().keySet()) {
251
252 final ParameterizedType parameterizedType = getParameterizedTypeSuperclass(c);
253
254 final Type currentType = parameterizedType.getActualTypeArguments()[0];
255
256 if (areSameTypes(currentClass, currentType)) {
257
258 @SuppressWarnings("unchecked")
259 final X x = getProcessCurrent((Class<? extends AbstractProcess<X, ?>>) c);
260
261 return x;
262 }
263 }
264
265 throw new IllegalArgumentException("Cannot find process class for currentClass: " + currentClass.getName());
266 }
267
268 protected final void ignoreThisOneInProcess() {
269
270 ignoreThisOnesThreadLocal.set(true);
271 }
272
273 private String getCurrentTestMethodName() {
274
275 for (final StackTraceElement ste : new Exception().getStackTrace()) {
276
277 final String methodName = ste.getMethodName();
278
279
280 for (final Method testMethod : testMethods) {
281
282 if (methodName.equals(testMethod.getName())) {
283
284 return methodName;
285 }
286 }
287 }
288
289 return null;
290 }
291
292 private static String composeTestSummary(final ProcessInstance processInstance, final String testMethodName) {
293
294 final int count = processInstance.process.getCurrentIndex();
295
296 final List<Throwable> methodErrors = processInstance.methodErrors.get(testMethodName);
297
298 final List<AssertionError> methodFailures = processInstance.methodFailures.get(testMethodName);
299
300 final String summary = "Runs: " + count
301 + ", Failures: " + methodFailures.size()
302 + ", Errors: " + methodErrors.size()
303 + " (+" + processInstance.errors.size() + ")";
304
305 return summary;
306 }
307
308 private static <T extends Throwable> T recomposeThrowable(final T error, final String summary) {
309
310 final StackTraceElement[] stackTrace = error.getStackTrace();
311
312 T newError = error;
313
314 final Class<? extends Throwable> errorClass = error.getClass();
315
316 Constructor<? extends Throwable> constructor;
317
318 try {
319
320 constructor = errorClass.getConstructor(String.class);
321
322 } catch (final Throwable e1) {
323
324 try {
325
326 constructor = errorClass.getConstructor(Object.class);
327
328 } catch (final Throwable e2) {
329
330 try {
331
332 constructor = errorClass.getConstructor(String.class, String.class, String.class);
333
334 } catch (final Throwable e3) {
335
336 constructor = null;
337 }
338 }
339 }
340
341 if (constructor != null) {
342
343 final String message = summary + ", Sample: " + error.getMessage();
344
345 try {
346
347 final Object o;
348
349 if (constructor.getParameterTypes().length == 1) {
350
351 o = constructor.newInstance(message);
352
353 } else {
354
355 o = constructor.newInstance(message, "", "");
356 }
357
358 @SuppressWarnings("unchecked")
359 final T e = (T) o;
360
361 newError = e;
362
363 } catch (final Throwable e) {
364
365
366 }
367 }
368
369 newError.setStackTrace(stackTrace);
370
371 return newError;
372 }
373
374 private final ProcessInstance getUniqueProcessInstance() {
375
376 final int instanceCount = processInstances.size();
377
378 switch (instanceCount) {
379 case 0:
380 throw new IllegalStateException("Cannot call getUniqueProcessInstance()"
381 + " with zero process.");
382
383 case 1:
384 return processInstances.values().iterator().next();
385
386 default:
387 break;
388 }
389
390 for (final Method testMethod : testMethods) {
391
392 final WhileProcessing processing = testMethod.getAnnotation(WhileProcessing.class);
393
394 if (processing != null) {
395
396 final Class<? extends AbstractProcess<?, ?>>[] processClasses = processing.value();
397
398 if (processClasses != null && processClasses.length == 1) {
399
400 return getInitializedProcessInstance(processClasses[0]);
401 }
402 }
403 }
404
405 throw new IllegalStateException("Cannot call getUniqueProcessInstance()" + " with more than one process: "
406 + processInstances.keySet().iterator().next().getSimpleName() + ", etc.");
407 }
408
409 protected final int getProcessCurrentIndex() throws Exception {
410
411 final ProcessInstance processInstance = getUniqueProcessInstance();
412
413 return getProcessCurrentIndex(processInstance);
414 }
415
416 protected final int getProcessCurrentIndex(final Class<? extends AbstractProcess<?, ?>> processClass)
417 throws Exception {
418
419 final ProcessInstance processInstance = getInitializedProcessInstance(processClass);
420
421 return getProcessCurrentIndex(processInstance);
422 }
423
424 private final int getProcessCurrentIndex(final ProcessInstance processInstance) throws Exception {
425
426 return processInstance.process.getCurrentIndex();
427 }
428
429 protected final <X> X getProcessCurrent(final Class<? extends AbstractProcess<X, ?>> processClass) throws Exception {
430
431 checkNotNull(processClass, "processClass");
432
433 final ProcessInstance processInstance = getInitializedProcessInstance(processClass);
434
435 if (!running) {
436
437 final String testMethodName = getCurrentTestMethodName();
438
439 if (testMethodName == null) {
440 throw new IllegalStateException("Calling getProcessCurrent() from outside a test method.");
441 }
442
443 final String summary = composeTestSummary(processInstance, testMethodName);
444
445 Throwable error = null;
446
447 final List<Throwable> methodErrors = processInstance.methodErrors.get(testMethodName);
448
449 if (!methodErrors.isEmpty()) {
450
451 error = methodErrors.get(0);
452 }
453
454 if (error == null) {
455
456 final List<AssertionError> methodFailures = processInstance.methodFailures.get(testMethodName);
457
458 if (!methodFailures.isEmpty()) {
459
460 final AssertionError failure = methodFailures.get(0);
461
462 throw recomposeThrowable(failure, summary);
463 }
464
465 if (!processInstance.errors.isEmpty()) {
466
467 error = processInstance.errors.get(0);
468 }
469 }
470
471 if (error != null) {
472
473 if (Error.class.isInstance(error)) {
474
475 throw recomposeThrowable((Error) error, summary);
476
477 } else if (Exception.class.isInstance(error)) {
478
479 throw recomposeThrowable((Exception) error, summary);
480
481 } else {
482
483 throw new RuntimeException(summary, error);
484 }
485 }
486 }
487
488 @SuppressWarnings("unchecked")
489 final X current = (X) processInstance.process.getCurrent();
490
491 return current;
492 }
493
494 private static boolean areSameTypes(final Class<?> c, final Type t) {
495
496 if (c.equals(t)) {
497
498 return true;
499 }
500
501 if (c.isArray() && GenericArrayType.class.isInstance(t)) {
502
503 if (c.getComponentType().equals(((GenericArrayType) t).getGenericComponentType())) {
504
505 return true;
506 }
507 }
508
509 return false;
510 }
511
512 protected final <X> X getProcessResultOfType(final Class<?> resultClass) throws Exception {
513
514 checkNotNull(resultClass, "resultClass");
515
516 for (final Class<? extends AbstractProcess<?, ?>> c : getProcessInstances().keySet()) {
517
518 final ParameterizedType parameterizedType = getParameterizedTypeSuperclass(c);
519
520 final Type resultType = parameterizedType.getActualTypeArguments()[1];
521
522 if (areSameTypes(resultClass, resultType)) {
523
524 @SuppressWarnings("unchecked")
525 final X x = getProcessResult((Class<? extends AbstractProcess<?, X>>) c);
526
527 return x;
528 }
529 }
530
531 throw new IllegalArgumentException("Cannot find process class for resultClass: " + resultClass.getName());
532 }
533
534 protected final <X> X getProcessResult(final Class<? extends AbstractProcess<?, X>> processClass) throws Exception {
535
536 checkNotNull(processClass, "processClass");
537
538 if (running || lockFile.exists()) {
539
540 throw new IllegalStateException("Process are still running.");
541 }
542
543 final ProcessInstance processInstance = getInitializedProcessInstance(processClass);
544
545 if (!processInstance.errors.isEmpty()) {
546
547 final Throwable error = processInstance.errors.get(0);
548
549 if (Error.class.isInstance(error)) {
550
551 throw (Error) error;
552
553 } else if (Exception.class.isInstance(error)) {
554
555 throw (Exception) error;
556
557 } else {
558
559 throw new RuntimeException(error);
560 }
561 }
562
563 @SuppressWarnings("unchecked")
564 final X result = (X) processInstance.process.getResult();
565
566 return result;
567 }
568
569 private static boolean running = false;
570
571 @Before
572 public final void setUpProcess() throws Exception {
573
574 if (!running) {
575
576 runProcess();
577 }
578 }
579
580 final File lockFile = new File("target", getClass().getSimpleName() + ".lock");
581
582 private static Set<Class<? extends TestsWithProcessesBefore>> haveBeenRun = new HashSet<Class<? extends TestsWithProcessesBefore>>();
583
584 private synchronized void runProcess() throws IOException, IllegalArgumentException, SecurityException,
585 InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
586
587 if (running) {
588
589 return;
590 }
591
592 if (lockFile.exists()) {
593
594 throw new IllegalStateException("Lock file exists (" + new DateTime(lockFile.lastModified()) + "): "
595 + lockFile.getCanonicalPath());
596 }
597
598 final Class<? extends TestsWithProcessesBefore> thisClass = getClass();
599
600 if (haveBeenRun.contains(thisClass)) {
601
602 return;
603 }
604
605 FileUtils.touch(lockFile);
606
607 running = true;
608
609 haveBeenRun.add(thisClass);
610
611 runInstanceProcess();
612
613 lockFile.delete();
614
615 running = false;
616 }
617
618 private ProcessInstance getInitializedProcessInstance(final Class<? extends AbstractProcess<?, ?>> processClass) {
619
620 final String processName = processClass.getSimpleName();
621
622 final ProcessInstance processInstance = getProcessInstances().get(processClass);
623
624 if (processInstance == null) {
625 throw new IllegalStateException("Process has not been initialized: " + processName);
626 }
627
628 return processInstance;
629 }
630
631 private Map<Class<? extends AbstractProcess<?, ?>>, ProcessInstance> getProcessInstances() {
632
633 final Class<? extends TestsWithProcessesBefore> thisClass = getClass();
634
635 final TestsWithProcessesBefore instance = instances.get(thisClass);
636
637 if (instance == null) {
638 throw new IllegalStateException("Instance has not been initialized for: " + thisClass);
639 }
640
641 return instance.processInstances;
642 }
643
644 private ProcessInstance getInitializedProcessInstance(final AbstractProcess<?, ?> process) {
645
646 final String processName = process.getClass().getSimpleName();
647
648 for (final Map.Entry<Class<? extends AbstractProcess<?, ?>>, ProcessInstance> entry : processInstances
649 .entrySet()) {
650
651 final Class<?> c = entry.getKey();
652
653 if (c.equals(process.getClass())) {
654
655 return entry.getValue();
656 }
657 }
658
659 throw new IllegalStateException("Process has not been initialized: " + processName);
660 }
661
662 private void runInstanceProcess() throws IOException, IllegalArgumentException, SecurityException,
663 InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
664
665 try {
666
667 for (final Class<? extends AbstractProcess<?, ?>> processClass : processClasses) {
668
669 final String processName = processClass.getSimpleName();
670
671 final ProcessInstance processInstance = getInitializedProcessInstance(processClass);
672
673 System.out.println("Executing process: " + processName + "...");
674
675 final long start = System.currentTimeMillis();
676
677 assertTrue("Error list is not empty.", processInstance.errors.isEmpty());
678
679 for (final Method testMethod : testMethods) {
680
681 final String testMethodName = testMethod.getName();
682
683 assertTrue("Error list is not empty for: " + testMethodName + "()", processInstance.methodErrors
684 .get(testMethodName).isEmpty());
685
686 assertTrue("Failure list is not empty for: " + testMethodName + "()",
687 processInstance.methodFailures.get(testMethodName).isEmpty());
688 }
689
690 boolean success = false;
691
692 try {
693
694 processInstance.process.execute();
695
696 success = true;
697
698 } catch (final Throwable e) {
699
700 success = false;
701
702 processInstance.errors.add(e);
703
704 e.printStackTrace(System.out);
705 }
706
707 final long elapsed = System.currentTimeMillis() - start;
708
709 System.out.println();
710
711 if (success) {
712
713 System.out.print("SUCCESS. Process " + processName + " done in " + elapsed + " ms.");
714
715 } else {
716
717 System.out.print("ERROR. In process " + processName + " after " + elapsed + " ms.");
718 }
719
720 processInstance.process.cliProgress.end();
721
722
723
724 emitProcessEntryReport(processInstance);
725
726 emitStandardReport(processInstance);
727 }
728
729 } finally {
730
731 releaseProcess();
732 }
733 }
734
735 private static final ThreadLocal<Boolean> ignoreThisOnesThreadLocal = new ThreadLocal<Boolean>();
736
737 final void runTests(final AbstractProcess<?, ?> process, final ProcessEntry processEntry) {
738
739 checkNotNull(process);
740
741 final ProcessInstance processInstance = getInitializedProcessInstance(process);
742
743 for (final Method testMethod : testMethods) {
744
745 final WhileProcessing whileProcessing = testMethod.getAnnotation(WhileProcessing.class);
746
747 final WillReportAfterProcesses willReportAfterProcesses = testMethod
748 .getAnnotation(WillReportAfterProcesses.class);
749
750 if (willReportAfterProcesses != null || whileProcessing == null) {
751 continue;
752 }
753
754 final Class<? extends AbstractProcess<?, ?>>[] whileProcessingProcesses = whileProcessing.value();
755
756 if (whileProcessingProcesses != null && whileProcessingProcesses.length != 0) {
757
758 boolean found = false;
759
760 for (final Class<? extends AbstractProcess<?, ?>> c : whileProcessingProcesses) {
761 if (c.isAssignableFrom(process.getClass())) {
762 found = true;
763 break;
764 }
765 }
766
767 if (!found) {
768 continue;
769 }
770 }
771
772 final String testMethodName = testMethod.getName();
773
774 final List<Throwable> errors = processInstance.methodErrors.get(testMethodName);
775
776 final List<AssertionError> failures = processInstance.methodFailures.get(testMethodName);
777
778 final List<ProcessEntryResult> results = processInstance.processEntryResults.get(testMethodName);
779
780 final ProcessEntryResult result = (processEntry == null) ? null : new ProcessEntryResult(processEntry);
781
782 final long start = System.currentTimeMillis();
783
784 ignoreThisOnesThreadLocal.set(false);
785
786 try {
787
788 testMethod.invoke(this);
789
790 } catch (final InvocationTargetException e) {
791
792 final Throwable cause = e.getTargetException();
793
794 if (cause == null) {
795
796 setResultError(result, e);
797
798 errors.add(e);
799
800 } else if (AssertionError.class.isInstance(cause)) {
801
802 setResultFailure(result, (AssertionError) cause);
803
804 failures.add((AssertionError) cause);
805
806 } else {
807
808 setResultError(result, e);
809
810 errors.add(cause);
811 }
812
813 } catch (final AssertionError e) {
814
815 setResultFailure(result, (AssertionError) e);
816
817 failures.add(e);
818
819 } catch (final Throwable e) {
820
821 setResultError(result, e);
822
823 errors.add(e);
824 }
825
826 if (result != null && !ignoreThisOnesThreadLocal.get()) {
827
828 results.add(result);
829 }
830
831 final long elapsed = System.currentTimeMillis() - start;
832
833 setResultElapsedMs(result, elapsed);
834 }
835 }
836
837 private static void setResultError(final ProcessEntryResult result, final Throwable e) {
838
839 if (result != null) {
840
841 result.setError(e);
842 }
843 }
844
845 private static void setResultFailure(final ProcessEntryResult result, final AssertionError e) {
846
847 if (result != null) {
848
849 result.setFailure(e);
850 }
851 }
852
853 private static void setResultElapsedMs(final ProcessEntryResult result, final long elapsed) {
854
855 if (result != null) {
856
857 result.setElapsedMs(elapsed);
858 }
859 }
860
861 private void emitStandardReport(final ProcessInstance processInstance) throws IllegalArgumentException,
862 SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException,
863 NoSuchMethodException {
864
865 final String processName = processInstance.process.getClass().getSimpleName();
866
867 final Report report = instantiateReport(processName, 1, getReportCount());
868
869 try {
870
871 final int errorCount = processInstance.errors.size();
872
873 switch (errorCount) {
874 case 0:
875 report.info("No error at process level.");
876 break;
877 case 1:
878 report.error("One error at process level.");
879 break;
880 default:
881 report.error(errorCount + " errors at process level.");
882 break;
883 }
884
885 final List<Throwable> totalMethodErrors = new ArrayList<Throwable>();
886 final List<AssertionError> totalMethodFailures = new ArrayList<AssertionError>();
887
888 final Set<String> testMethodNames = processInstance.methodErrors.keySet();
889
890 for (final String methodName : testMethodNames) {
891
892 totalMethodErrors.addAll(processInstance.methodErrors.get(methodName));
893 totalMethodFailures.addAll(processInstance.methodFailures.get(methodName));
894 }
895
896 final int totalMethodErrorCount = totalMethodErrors.size();
897 final int totalMethodFailureCount = totalMethodFailures.size();
898
899 switch (totalMethodFailureCount) {
900 case 0:
901 report.info("No failure at test level.");
902 break;
903 case 1:
904 report.error("One failure at test level.");
905 break;
906 default:
907 report.error(totalMethodFailureCount + " failures at test level.");
908 break;
909 }
910
911 switch (totalMethodErrorCount) {
912 case 0:
913 report.info("No error at test level.");
914 break;
915 case 1:
916 report.error("One error at test level.");
917 break;
918 default:
919 report.error(totalMethodErrorCount + " errors at test level.");
920 break;
921 }
922
923
924
925 report.info("Summary:");
926
927 report.info("Test Method", "Runs", "Failures", "Errors");
928
929 final int count = processInstance.process.getCurrentIndex();
930
931 for (final String testMethodName : new TreeSet<String>(testMethodNames)) {
932
933 final List<Throwable> methodErrors = processInstance.methodErrors.get(testMethodName);
934
935 final List<AssertionError> methodFailures = processInstance.methodFailures.get(testMethodName);
936
937 report.infoDetail(testMethodName + "()", count, methodFailures.size(), methodErrors.size());
938 }
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969 for (final String testMethodName : new TreeSet<String>(testMethodNames)) {
970
971 final String summary = composeTestSummary(processInstance, testMethodName);
972
973 final List<Throwable> methodErrors = processInstance.methodErrors.get(testMethodName);
974
975 final List<AssertionError> methodFailures = processInstance.methodFailures.get(testMethodName);
976
977 if (methodErrors.isEmpty() && methodFailures.isEmpty()) {
978
979
980
981 } else {
982
983 report.error(testMethodName + "(): " + summary);
984 for (int i = 0; i < methodFailures.size() && i < MAX; ++i) {
985 report.errorDetail(methodFailures.get(i));
986 }
987 if (methodFailures.size() > MAX) {
988 report.errorDetail("(...)");
989 }
990
991 for (int i = 0; i < methodErrors.size() && i < MAX; ++i) {
992 report.errorDetail(methodErrors.get(i));
993 }
994 if (methodErrors.size() > MAX) {
995 report.errorDetail("(...)");
996 }
997 }
998 }
999
1000 } finally {
1001
1002 report.send();
1003 }
1004 }
1005
1006 private static void emitProcessEntryReport(final ProcessInstance processInstance) throws IOException {
1007
1008 final File file = new File("target/processentry-report/" + processInstance.process.getClass().getSimpleName()
1009 + ".xml");
1010
1011 FileUtils.forceMkdir(file.getParentFile());
1012
1013 final OutputStream os = new FileOutputStream(file);
1014 try {
1015
1016 final PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, UTF_8));
1017
1018 pw.println("<processentry-report>");
1019
1020 for (final Map.Entry<String, List<ProcessEntryResult>> e : processInstance.processEntryResults.entrySet()) {
1021
1022 final String testMethodName = e.getKey();
1023
1024 final List<ProcessEntryResult> results = e.getValue();
1025
1026 pw.println("<testMethod name=\"" + testMethodName + "\">");
1027
1028 if (results != null) {
1029
1030 for (final ProcessEntryResult result : results) {
1031
1032 pw.print("\t<processentry id=\"" + xmlEncode(result.getProcessEntryId()) + "\" elapsed=\""
1033 + result.getElapsedMs() + "\"");
1034
1035 printReportXmlAttribute(pw, "errorClassName", result.getErrorClassName());
1036 printReportXmlAttribute(pw, "errorMessage", result.getErrorMessage());
1037 printReportXmlAttribute(pw, "failureClassName", result.getFailureClassName());
1038 printReportXmlAttribute(pw, "failureMessage", result.getFailureMessage());
1039
1040 pw.println("/>");
1041 }
1042 }
1043
1044 pw.println("</testMethod>");
1045 }
1046
1047 pw.println("</processentry-report>");
1048
1049 pw.flush();
1050
1051 } finally {
1052 os.close();
1053 }
1054 }
1055
1056 private static void printReportXmlAttribute(final PrintWriter pw, final String name, final String value) {
1057
1058 if (value != null) {
1059 pw.print(" " + name + "=\"" + xmlEncode(value) + "\"");
1060 }
1061 }
1062
1063 private static String xmlEncode(final String s) {
1064
1065 return s.replace("&", "&").replace("\"", """).replace("<", "<").replace(">", ">");
1066 }
1067
1068 private static final int MAX = 10;
1069
1070 private int reportIndex = 2;
1071
1072 protected final Report createReport() {
1073
1074 final String testMethodName = getCurrentTestMethodName();
1075
1076 final Report report;
1077
1078 try {
1079
1080 report = instantiateReport(getClass().getSimpleName() + "." + testMethodName + "()", reportIndex,
1081 getReportCount());
1082
1083 } catch (final RuntimeException e) {
1084 throw e;
1085 } catch (final Exception e) {
1086 throw new RuntimeException(e);
1087 }
1088
1089 ++reportIndex;
1090
1091 return report;
1092 }
1093
1094 private final int getReportCount() {
1095
1096 int reportCount = 1;
1097
1098 for (final Method testMethod : testMethods) {
1099
1100 if (testMethod.isAnnotationPresent(WillReportAfterProcesses.class)) {
1101
1102 ++reportCount;
1103 }
1104 }
1105
1106 return reportCount;
1107 }
1108
1109 private static boolean hasConstructor(final Class<?> c, final Class<?>... params) {
1110
1111 try {
1112
1113 c.getConstructor(params);
1114
1115 return true;
1116
1117 } catch (final Exception e) {
1118
1119
1120 }
1121
1122 return false;
1123 }
1124
1125 private Report instantiateReport(final String name, final int index, final int count)
1126 throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException,
1127 InvocationTargetException, NoSuchMethodException {
1128
1129 final Class<? extends TestsWithProcessesBefore> thisClass = getClass();
1130
1131 final ReportTo reportTo = thisClass.getAnnotation(ReportTo.class);
1132
1133 if (reportTo == null) {
1134
1135 return new SystemOutReport(name, index, count);
1136
1137 } else {
1138
1139 final List<Report> reports = new ArrayList<Report>();
1140
1141 for (final Class<? extends Report> reportClass : reportTo.value()) {
1142
1143 final Report r;
1144
1145 if (hasConstructor(reportClass, String.class, int.class, int.class)) {
1146
1147 r = reportClass.getConstructor(String.class, int.class, int.class).newInstance(name, index, count);
1148
1149 } else if (hasConstructor(reportClass, String.class)) {
1150
1151 r = reportClass.getConstructor(String.class).newInstance(name);
1152
1153 } else {
1154
1155 r = reportClass.newInstance();
1156 }
1157
1158 reports.add(r);
1159 }
1160
1161 return new CompositeReport(reports);
1162 }
1163 }
1164 }