6161import java .util .function .Consumer ;
6262
6363import static io .qameta .allure .AllureConstants .ATTACHMENT_FILE_SUFFIX ;
64+ import static io .qameta .allure .util .ResultsUtils .firstNonEmpty ;
6465import static io .qameta .allure .util .ResultsUtils .getStatus ;
6566import static io .qameta .allure .util .ResultsUtils .getStatusDetails ;
6667import static io .qameta .allure .util .ServiceLoaderUtils .load ;
@@ -230,7 +231,7 @@ public void scheduleTest(final AllureExternalKey key, final TestResult result) {
230231 LOGGER .warn (KEY_ALREADY_EXISTS , SCHEDULE_TEST , key );
231232 return ;
232233 }
233- if (isEmpty (result .getUuid ())) {
234+ if (firstNonEmpty (result .getUuid ()). isEmpty ( )) {
234235 result .setUuid (UUID .randomUUID ().toString ());
235236 }
236237 notifier .beforeTestSchedule (result );
@@ -584,22 +585,12 @@ public void startStage(final StepResult result) {
584585 * Closes consecutive open stages on top of the calling thread's stack. A stage with no status is marked passed.
585586 */
586587 private void closeOpenStages () {
587- while (true ) {
588- final Optional <AllureExternalKey > current = threadContext .getCurrentStep ();
589- if (current .isEmpty ()) {
590- return ;
591- }
592- final Object item = items .get (current .get ());
593- if (!(item instanceof StepItem ) || !((StepItem ) item ).stage ()) {
594- return ;
595- }
596- finalizeStage ((StepItem ) item );
597- stopStep (current .get (), true );
598- }
588+ closeOpenStagesAbove (null );
599589 }
600590
601591 /**
602- * Closes consecutive open stages bound above the given step on the calling thread's stack.
592+ * Closes consecutive open stages bound above the given step on the calling thread's stack, or all consecutive
593+ * top stages when the step is {@code null}. A stage with no status is marked passed.
603594 */
604595 private void closeOpenStagesAbove (final AllureExternalKey key ) {
605596 while (true ) {
@@ -611,17 +602,13 @@ private void closeOpenStagesAbove(final AllureExternalKey key) {
611602 if (!(item instanceof StepItem ) || !((StepItem ) item ).stage ()) {
612603 return ;
613604 }
614- finalizeStage ((StepItem ) item );
605+ if (Objects .isNull (((StepItem ) item ).result ().getStatus ())) {
606+ ((StepItem ) item ).result ().setStatus (Status .PASSED );
607+ }
615608 stopStep (current .get (), true );
616609 }
617610 }
618611
619- private static void finalizeStage (final StepItem item ) {
620- if (Objects .isNull (item .result ().getStatus ())) {
621- item .result ().setStatus (Status .PASSED );
622- }
623- }
624-
625612 /**
626613 * Updates step by specified key.
627614 *
@@ -837,7 +824,7 @@ private CompletableFuture<Void> addAttachmentAsync(final AllureExternalKey key,
837824
838825 /**
839826 * Adds an attachment wrapped in its own instant step under the current executable — the default representation
840- * for user-facing attachments. Falls back to a plain attachment while a lifecycle listener callback is running .
827+ * for user-facing attachments. Safe to call from listener callbacks: the wrapper step emits no further events .
841828 * Takes no effect if no executable is running.
842829 *
843830 * @param name the name of attachment
@@ -852,10 +839,6 @@ public void addAttachmentStep(final String name, final String type,
852839 LOGGER .warn (NO_CONTEXT_FOR_ATTACHMENT );
853840 return ;
854841 }
855- if (LifecycleNotifier .isListenerCallbackRunning ()) {
856- addAttachment (current .get (), name , type , content , options );
857- return ;
858- }
859842 final AllureExternalKey key = AllureExternalKey .random (AllureLifecycle .class );
860843 startStep (current .get (), key , new StepResult ().setName (attachmentStepName (name )), true );
861844 if (!items .containsKey (key )) {
@@ -876,8 +859,7 @@ public void addAttachmentStep(final String name, final String type,
876859
877860 /**
878861 * Adds an attachment wrapped in its own instant step under the specified parent — the default representation
879- * for user-facing attachments. Pure manual linkage: no thread state is touched. Falls back to a plain
880- * attachment while a lifecycle listener callback is running.
862+ * for user-facing attachments. Pure manual linkage: no thread state is touched.
881863 *
882864 * @param parentKey the external parent key
883865 * @param name the name of attachment
@@ -887,11 +869,6 @@ public void addAttachmentStep(final String name, final String type,
887869 */
888870 public void addAttachmentStep (final AllureExternalKey parentKey , final String name , final String type ,
889871 final InputStream content , final AttachmentOptions options ) {
890- if (LifecycleNotifier .isListenerCallbackRunning ()) {
891- // a listener adding attachments must not spawn steps (and further listener callbacks) recursively
892- addAttachment (parentKey , name , type , content , options );
893- return ;
894- }
895872 final AllureExternalKey key = AllureExternalKey .random (AllureLifecycle .class );
896873 startStep (parentKey , key , new StepResult ().setName (attachmentStepName (name )), false );
897874 if (!items .containsKey (key )) {
@@ -913,8 +890,7 @@ public void addAttachmentStep(final AllureExternalKey parentKey, final String na
913890 /**
914891 * Adds an async attachment wrapped in its own instant step under the current executable. The attachment content
915892 * is awaited before the owning test or scope is written; a failed body marks the step broken before the owner
916- * is serialized. Falls back to a plain async attachment while a lifecycle listener callback is running. Takes
917- * no effect if no executable is running.
893+ * is serialized. Takes no effect if no executable is running.
918894 *
919895 * @param name the name of attachment
920896 * @param type the content type of attachment
@@ -930,9 +906,6 @@ public CompletableFuture<Void> addAttachmentStepAsync(final String name, final S
930906 LOGGER .warn (NO_CONTEXT_FOR_ATTACHMENT );
931907 return CompletableFuture .completedFuture (null );
932908 }
933- if (LifecycleNotifier .isListenerCallbackRunning ()) {
934- return addAttachmentAsync (current .get (), name , type , body , options , null );
935- }
936909 final AllureExternalKey key = AllureExternalKey .random (AllureLifecycle .class );
937910 final StepResult step = new StepResult ()
938911 .setName (attachmentStepName (name ))
@@ -958,8 +931,7 @@ public CompletableFuture<Void> addAttachmentStepAsync(final String name, final S
958931 /**
959932 * Adds an async attachment wrapped in its own instant step under the specified parent. Pure manual linkage: no
960933 * thread state is touched. The attachment content is awaited before the owning test or scope is written; a
961- * failed body marks the step broken before the owner is serialized. Falls back to a plain async attachment
962- * while a lifecycle listener callback is running.
934+ * failed body marks the step broken before the owner is serialized.
963935 *
964936 * @param parentKey the external parent key
965937 * @param name the name of attachment
@@ -972,9 +944,6 @@ public CompletableFuture<Void> addAttachmentStepAsync(final AllureExternalKey pa
972944 final String type ,
973945 final CompletionStage <? extends InputStream > body ,
974946 final AttachmentOptions options ) {
975- if (LifecycleNotifier .isListenerCallbackRunning ()) {
976- return addAttachmentAsync (parentKey , name , type , body , options );
977- }
978947 final AllureExternalKey key = AllureExternalKey .random (AllureLifecycle .class );
979948 final StepResult step = new StepResult ()
980949 .setName (attachmentStepName (name ))
@@ -998,7 +967,7 @@ public CompletableFuture<Void> addAttachmentStepAsync(final AllureExternalKey pa
998967 }
999968
1000969 private static String attachmentStepName (final String name ) {
1001- return isEmpty (name ) ? "Attachment" : name ;
970+ return firstNonEmpty (name ). orElse ( "Attachment" ) ;
1002971 }
1003972
1004973 private Optional <String > addAttachmentLink (final AllureExternalKey key , final String name ,
@@ -1011,8 +980,8 @@ private Optional<String> addAttachmentLink(final AllureExternalKey key, final St
1011980 return Optional .empty ();
1012981 }
1013982 final Attachment attachment = new Attachment ()
1014- .setName (isEmpty (name ) ? null : name )
1015- .setType (isEmpty (type ) ? null : type )
983+ .setName (firstNonEmpty (name ). orElse ( null ) )
984+ .setType (firstNonEmpty (type ). orElse ( null ) )
1016985 .setSource (createAttachmentSource (type , options ));
1017986 synchronized (item ) {
1018987 ((WithAttachments ) model ).getAttachments ().add (attachment );
@@ -1268,7 +1237,7 @@ private void sweepOwnedSteps(final AllureExternalKey ownerKey) {
12681237 }
12691238
12701239 private void addChild (final ScopeItem scope , final String childUuid ) {
1271- if (isEmpty (childUuid )) {
1240+ if (firstNonEmpty (childUuid ). isEmpty ( )) {
12721241 return ;
12731242 }
12741243 synchronized (scope ) {
@@ -1327,19 +1296,11 @@ private static TestResultContainer toScopeContainer(final ScopeResult scope) {
13271296 * detaches any list alias the consumer may have retained.
13281297 */
13291298 private static void normalizeScope (final ScopeResult scope ) {
1330- scope .setTests (mutableList (scope .getTests ()));
1331- scope .setFixtures (mutableList (scope .getFixtures ()));
1332- scope .setLabels (mutableList (scope .getLabels ()));
1333- scope .setLinks (mutableList (scope .getLinks ()));
1334- scope .setParameters (mutableList (scope .getParameters ()));
1335- }
1336-
1337- private static <T > List <T > mutableList (final List <T > values ) {
1338- return Objects .isNull (values ) ? new ArrayList <>() : new ArrayList <>(values );
1339- }
1340-
1341- private static boolean isEmpty (final String s ) {
1342- return Objects .isNull (s ) || s .isEmpty ();
1299+ scope .setTests (new ArrayList <>(Objects .requireNonNullElse (scope .getTests (), List .of ())));
1300+ scope .setFixtures (new ArrayList <>(Objects .requireNonNullElse (scope .getFixtures (), List .of ())));
1301+ scope .setLabels (new ArrayList <>(Objects .requireNonNullElse (scope .getLabels (), List .of ())));
1302+ scope .setLinks (new ArrayList <>(Objects .requireNonNullElse (scope .getLinks (), List .of ())));
1303+ scope .setParameters (new ArrayList <>(Objects .requireNonNullElse (scope .getParameters (), List .of ())));
13431304 }
13441305
13451306 // ── Items ────────────────────────────────────────────────────────────────────────────────
0 commit comments