1717
1818import io .qameta .allure .Allure ;
1919import io .qameta .allure .AllureLifecycle ;
20- import io .qameta .allure .model .Status ;
21- import io .qameta .allure .model .StepResult ;
22- import io .qameta .allure .util .ObjectUtils ;
20+ import org .assertj .core .api .AbstractAssert ;
2321import org .aspectj .lang .JoinPoint ;
22+ import org .aspectj .lang .ProceedingJoinPoint ;
2423import org .aspectj .lang .annotation .After ;
2524import org .aspectj .lang .annotation .AfterReturning ;
26- import org .aspectj .lang .annotation .AfterThrowing ;
25+ import org .aspectj .lang .annotation .Around ;
2726import org .aspectj .lang .annotation .Aspect ;
28- import org .aspectj .lang .annotation .Before ;
2927import org .aspectj .lang .annotation .Pointcut ;
3028import org .aspectj .lang .reflect .MethodSignature ;
31- import org .slf4j .Logger ;
32- import org .slf4j .LoggerFactory ;
3329
34- import java .util .UUID ;
35- import java .util .stream .Collectors ;
36- import java .util .stream .Stream ;
37-
38- import static io .qameta .allure .util .ResultsUtils .getStatus ;
39- import static io .qameta .allure .util .ResultsUtils .getStatusDetails ;
30+ import java .util .function .Supplier ;
4031
4132/**
33+ * Captures user-side AssertJ factories and fluent calls, then delegates assertion-chain state
34+ * to {@link AssertJRecorder}.
35+ *
4236 * @author charlie (Dmitry Baev).
4337 * @author sskorol (Sergey Korol).
4438 */
4539@ SuppressWarnings ("all" )
4640@ Aspect
4741public class AllureAspectJ {
4842
49- private static final Logger LOGGER = LoggerFactory .getLogger (AllureAspectJ .class );
50-
5143 private static InheritableThreadLocal <AllureLifecycle > lifecycle = new InheritableThreadLocal <AllureLifecycle >() {
5244 @ Override
5345 protected AllureLifecycle initialValue () {
5446 return Allure .getLifecycle ();
5547 }
5648 };
5749
58- @ Pointcut ("execution(!private org.assertj.core.api.AbstractAssert.new(..))" )
59- public void anyAssertCreation () {
50+ private static final ThreadLocal <AssertJRecorder > RECORDER = ThreadLocal .withInitial (AssertJRecorder ::new );
51+
52+ private static final ThreadLocal <Boolean > RECORDING_MUTED = ThreadLocal .withInitial (() -> false );
53+
54+ @ Pointcut ("("
55+ + "call(public static * org.assertj.core.api.Assertions*.assertThat*(..))"
56+ + " || call(public static * org.assertj.core.api.BDDAssertions*.then*(..))"
57+ + " || call(public * org.assertj.core.api.*SoftAssertionsProvider+.assertThat*(..))"
58+ + " || call(public * org.assertj.core.api.*SoftAssertionsProvider+.then*(..))"
59+ + ")" )
60+ public void assertFactoryCall () {
6061 //pointcut body, should be empty
6162 }
6263
63- @ Pointcut ("execution(* org.assertj.core.api.AssertJProxySetup.*(..))" )
64- public void proxyMethod () {
64+ @ Pointcut ("("
65+ + "call(public * org.assertj.core.api.AbstractAssert+.*(..))"
66+ + " || call(public * org.assertj.core.api.Assert+.*(..))"
67+ + " || call(public * org.assertj.core.api.Descriptable+.*(..))"
68+ + ")"
69+ + " && target(assertion)" )
70+ public void assertOperationCall (final AbstractAssert <?, ?> assertion ) {
6571 //pointcut body, should be empty
6672 }
6773
68- @ Pointcut ("execution(public * org.assertj.core.api.AbstractAssert+.*(..)) && !proxyMethod( )" )
69- public void anyAssert () {
74+ @ Pointcut ("!within( org.assertj..*) && !within(io.qameta.allure.assertj.AllureAspectJ )" )
75+ public void userCodeCall () {
7076 //pointcut body, should be empty
7177 }
7278
73- @ After ("anyAssertCreation()" )
74- public void logAssertCreation (final JoinPoint joinPoint ) {
75- final String actual = joinPoint .getArgs ().length > 0
76- ? ObjectUtils .toString (joinPoint .getArgs ()[0 ])
77- : "<?>" ;
78- final String uuid = UUID .randomUUID ().toString ();
79- final String name = String .format ("assertThat \' %s\' " , actual );
80-
81- final StepResult result = new StepResult ()
82- .setName (name )
83- .setStatus (Status .PASSED );
79+ @ AfterReturning (pointcut = "assertFactoryCall() && userCodeCall()" , returning = "result" )
80+ public void logAssertCreation (final JoinPoint joinPoint , final Object result ) {
81+ if (isRecordingMuted () || !(result instanceof AbstractAssert )) {
82+ return ;
83+ }
8484
85- getLifecycle (). startStep ( uuid , result ) ;
86- getLifecycle ().stopStep ( uuid );
85+ final AbstractAssert <?, ?> assertion = ( AbstractAssert <?, ?>) result ;
86+ getRecorder ().assertionCreated ( getLifecycle (), assertion , firstArgumentOf ( joinPoint ) );
8787 }
8888
89- @ Before ("anyAssert()" )
90- public void stepStart (final JoinPoint joinPoint ) {
91- final MethodSignature methodSignature = (MethodSignature ) joinPoint .getSignature ();
92-
93- final String uuid = UUID .randomUUID ().toString ();
94- final String name = joinPoint .getArgs ().length > 0
95- ? String .format ("%s \' %s\' " , methodSignature .getName (), arrayToString (joinPoint .getArgs ()))
96- : methodSignature .getName ();
97-
98- final StepResult result = new StepResult ()
99- .setName (name );
100-
101- getLifecycle ().startStep (uuid , result );
102- }
89+ @ Around ("assertOperationCall(assertion) && userCodeCall()" )
90+ public Object logAssertOperation (final ProceedingJoinPoint joinPoint ,
91+ final AbstractAssert <?, ?> assertion ) throws Throwable {
92+ final String methodName = getMethodName (joinPoint );
93+ if (isRecordingMuted () || getRecorder ().isIgnored (methodName )) {
94+ return joinPoint .proceed ();
95+ }
10396
104- @ AfterThrowing (pointcut = "anyAssert()" , throwing = "e" )
105- public void stepFailed (final Throwable e ) {
106- getLifecycle ().updateStep (s -> s
107- .setStatus (getStatus (e ).orElse (Status .BROKEN ))
108- .setStatusDetails (getStatusDetails (e ).orElse (null )));
109- getLifecycle ().stopStep ();
97+ final AssertJOperation operation = getRecorder ().startOperation (
98+ getLifecycle (),
99+ assertion ,
100+ methodName ,
101+ joinPoint .getArgs ()
102+ );
103+ try {
104+ final Object result = joinPoint .proceed ();
105+ getRecorder ().operationPassed (operation , result );
106+ return result ;
107+ } catch (Throwable throwable ) {
108+ getRecorder ().operationFailed (operation , throwable );
109+ throw throwable ;
110+ }
110111 }
111112
112- @ AfterReturning ( pointcut = "anyAssert()" )
113- public void stepStop () {
114- getLifecycle (). updateStep ( s -> s . setStatus ( Status . PASSED ));
115- getLifecycle ().stopStep ( );
113+ @ After ( "execution(public void org.assertj.core.api.DefaultAssertionErrorCollector.collectAssertionError("
114+ + "java.lang.AssertionError)) && args(error)" )
115+ public void softAssertionFailed ( final AssertionError error ) {
116+ getRecorder ().softAssertionFailed ( error );
116117 }
117118
118119 /**
@@ -122,15 +123,40 @@ public void stepStop() {
122123 */
123124 public static void setLifecycle (final AllureLifecycle allure ) {
124125 lifecycle .set (allure );
126+ clearContext ();
125127 }
126128
127129 public static AllureLifecycle getLifecycle () {
128130 return lifecycle .get ();
129131 }
130132
131- private static String arrayToString (final Object ... array ) {
132- return Stream .of (array )
133- .map (ObjectUtils ::toString )
134- .collect (Collectors .joining (" " ));
133+ public static void clearContext () {
134+ RECORDER .remove ();
135+ }
136+
137+ static <T > T withoutRecording (final Supplier <T > supplier ) {
138+ final boolean previous = RECORDING_MUTED .get ();
139+ RECORDING_MUTED .set (true );
140+ try {
141+ return supplier .get ();
142+ } finally {
143+ RECORDING_MUTED .set (previous );
144+ }
145+ }
146+
147+ private static AssertJRecorder getRecorder () {
148+ return RECORDER .get ();
149+ }
150+
151+ private static boolean isRecordingMuted () {
152+ return RECORDING_MUTED .get ();
153+ }
154+
155+ private static Object firstArgumentOf (final JoinPoint joinPoint ) {
156+ return joinPoint .getArgs ().length == 0 ? null : joinPoint .getArgs ()[0 ];
157+ }
158+
159+ private static String getMethodName (final ProceedingJoinPoint joinPoint ) {
160+ return ((MethodSignature ) joinPoint .getSignature ()).getMethod ().getName ();
135161 }
136162}
0 commit comments