4444import org .spockframework .runtime .model .ErrorInfo ;
4545import org .spockframework .runtime .model .FeatureInfo ;
4646import org .spockframework .runtime .model .IterationInfo ;
47- import org .spockframework .runtime .model .MethodInfo ;
4847import org .spockframework .runtime .model .MethodKind ;
4948import org .spockframework .runtime .model .SpecInfo ;
5049import org .spockframework .runtime .model .TestTag ;
6564import java .util .stream .Collectors ;
6665import java .util .stream .IntStream ;
6766
67+ import static io .qameta .allure .util .ResultsUtils .ALLURE_ID_LABEL_NAME ;
6868import static io .qameta .allure .util .ResultsUtils .createFrameworkLabel ;
6969import static io .qameta .allure .util .ResultsUtils .createHostLabel ;
7070import static io .qameta .allure .util .ResultsUtils .createLanguageLabel ;
8989 *
9090 * <p>Register this extension with Spock to convert specification, feature, iteration, fixture, and error events into Allure results. The constructor accepting a test plan enables Allure test plan filtering before execution.</p>
9191 */
92- @ SuppressWarnings ("PMD.GodClass" )
92+ @ SuppressWarnings ({ "PMD.GodClass" , "PMD.TooManyMethods" } )
9393public class AllureSpock2 extends AbstractRunListener implements IGlobalExtension , IBlockListener {
9494
9595 private static final String BLOCK = "block" ;
@@ -173,44 +173,62 @@ public void visitSpec(final SpecInfo spec) {
173173 */
174174 @ Override
175175 public void beforeIteration (final IterationInfo iteration ) {
176- final String uuid = UUID .randomUUID ().toString ();
177-
178176 final FeatureInfo feature = iteration .getFeature ();
179- final MethodInfo methodInfo = feature .getFeatureMethod ();
180- final Method method = methodInfo .getReflection ();
177+ final List <Parameter > parameters = getParameters (feature .getDataVariables (), iteration .getDataValues ());
178+ final TestResult result = createTestResult (
179+ feature ,
180+ iteration .getName (),
181+ iteration .getDisplayName (),
182+ parameters
183+ );
184+ final String uuid = result .getUuid ();
185+
186+ testResults .set (uuid );
187+ final AllureExternalKey testKey = testKey (uuid );
188+ getLifecycle ().scheduleTest (testKey , result );
189+ getLifecycle ().addDefaultLabels (testKey , getDefaultLabels (feature .getSpec ()));
190+ getLifecycle ().startTest (testKey );
191+ blockSteps .set (new IdentityHashMap <>());
192+ activeBlockStep .remove ();
193+
194+ }
195+
196+ private TestResult createTestResult (final FeatureInfo feature ,
197+ final String testCaseName ,
198+ final String displayName ,
199+ final List <Parameter > parameters ) {
200+ return createTestResult (
201+ feature .getSpec (),
202+ feature .getFeatureMethod ().getReflection (),
203+ feature .getTestTags (),
204+ feature .getName (),
205+ testCaseName ,
206+ displayName ,
207+ parameters
208+ );
209+ }
210+
211+ private TestResult createTestResult (final SpecInfo specInfo ,
212+ final Method method ,
213+ final Set <TestTag > testTags ,
214+ final String testMethodName ,
215+ final String testCaseName ,
216+ final String displayName ,
217+ final List <Parameter > parameters ) {
181218 final Set <Label > featureLabels = AnnotationUtils .getLabels (method );
182219 final Set <Link > featureLinks = AnnotationUtils .getLinks (method );
183- final SpecInfo specInfo = feature .getSpec ();
184220 final Class <?> clazz = specInfo .getReflection ();
185221 final Set <Label > specLabels = AnnotationUtils .getLabels (clazz );
186222 final Set <Link > specLinks = AnnotationUtils .getLinks (clazz );
187223 final boolean flaky = AnnotationUtils .isFlaky (method ) || AnnotationUtils .isFlaky (clazz );
188224 final boolean muted = AnnotationUtils .isMuted (method ) || AnnotationUtils .isMuted (clazz );
189225
190- final List <Parameter > parameters = getParameters (feature .getDataVariables (), iteration .getDataValues ());
191226 final SpecInfo subSpec = specInfo .getSubSpec ();
192227 final SpecInfo superSpec = specInfo .getSuperSpec ();
193228 final String packageName = specInfo .getPackage ();
194- final String specName = specInfo .getName ();
195- final String testClassName = feature .getSpec ().getReflection ().getName ();
196- // the declared feature name is the source-level identity of the feature method,
197- // stable across data-driven iterations, unlike the resolved iteration display name
198- final String testMethodName = feature .getName ();
199- final String displayName = iteration .getDisplayName ();
200-
201- final List <Label > defaultLabels = new ArrayList <>(
202- Collections .singletonList (createSuiteLabel (specName ))
203- );
229+ final String testClassName = specInfo .getReflection ().getName ();
204230
205- if (Objects .nonNull (subSpec )) {
206- defaultLabels .add (createSubSuiteLabel (subSpec .getName ()));
207- }
208-
209- if (Objects .nonNull (superSpec )) {
210- defaultLabels .add (createParentSuiteLabel (superSpec .getName ()));
211- }
212-
213- final List <Label > testTags = feature .getTestTags ().stream ()
231+ final List <Label > tagLabels = testTags .stream ()
214232 .map (TestTag ::getValue )
215233 .map (ResultsUtils ::createTagLabel )
216234 .collect (Collectors .toList ());
@@ -226,7 +244,7 @@ public void beforeIteration(final IterationInfo iteration) {
226244 createLanguageLabel ("java" )
227245 )
228246 );
229- labels .addAll (testTags );
247+ labels .addAll (tagLabels );
230248 labels .addAll (featureLabels );
231249 labels .addAll (specLabels );
232250 AnnotationUtils .getSeverity (method )
@@ -239,23 +257,23 @@ public void beforeIteration(final IterationInfo iteration) {
239257 final List <Link > links = new ArrayList <>(featureLinks );
240258 links .addAll (specLinks );
241259
242- final String qualifiedName = getQualifiedName (iteration );
260+ final String qualifiedName = getQualifiedName (specInfo . getReflection (). getName (), testCaseName );
243261 final List <String > titlePath = new ArrayList <>(createTitlePathFromPackage (packageName ));
244262 titlePath .addAll (
245263 createTitlePath (
246264 Objects .nonNull (superSpec ) ? superSpec .getName () : null ,
247- specName ,
265+ specInfo . getName () ,
248266 Objects .nonNull (subSpec ) ? subSpec .getName () : null
249267 )
250268 );
251269 final TestResult result = new TestResult ()
252- .setUuid (uuid )
253- .setTestCaseName (iteration . getName () )
270+ .setUuid (UUID . randomUUID (). toString () )
271+ .setTestCaseName (testCaseName )
254272 .setTestCaseId (md5 (qualifiedName ))
255273 .setFullName (qualifiedName )
256274 .setTitlePath (titlePath )
257275 .setName (
258- firstNonEmpty (displayName , iteration . getName () , qualifiedName )
276+ firstNonEmpty (displayName , testCaseName , qualifiedName )
259277 .orElse ("Unknown" )
260278 )
261279 .setStatusDetails (
@@ -274,18 +292,91 @@ public void beforeIteration(final IterationInfo iteration) {
274292 result ::setDescriptionHtml
275293 );
276294
277- testResults . set ( uuid ) ;
278- final AllureExternalKey testKey = testKey ( uuid );
279- getLifecycle (). scheduleTest ( testKey , result );
280- getLifecycle (). addDefaultLabels ( testKey , defaultLabels );
281- getLifecycle (). startTest ( testKey );
282- blockSteps . set ( new IdentityHashMap <>());
283- activeBlockStep . remove ( );
295+ return result ;
296+ }
297+
298+ private List < Label > getDefaultLabels ( final SpecInfo specInfo ) {
299+ final List < Label > defaultLabels = new ArrayList <>(
300+ Collections . singletonList ( createSuiteLabel ( specInfo . getName ()))
301+ );
284302
303+ final SpecInfo subSpec = specInfo .getSubSpec ();
304+ if (Objects .nonNull (subSpec )) {
305+ defaultLabels .add (createSubSuiteLabel (subSpec .getName ()));
306+ }
307+
308+ final SpecInfo superSpec = specInfo .getSuperSpec ();
309+ if (Objects .nonNull (superSpec )) {
310+ defaultLabels .add (createParentSuiteLabel (superSpec .getName ()));
311+ }
312+
313+ return defaultLabels ;
314+ }
315+
316+ private void reportSetupSpecFailure (final IMethodInvocation invocation ,
317+ final String containerUuid ,
318+ final String fixtureName ,
319+ final Throwable throwable ) {
320+ final StatusDetails failureDetails = getStatusDetails (throwable ).orElse (null );
321+ final SpecInfo fixtureSpec = invocation .getSpec ();
322+ final TestResult configurationResult = createTestResult (
323+ fixtureSpec ,
324+ invocation .getMethod ().getReflection (),
325+ Collections .emptySet (),
326+ fixtureName ,
327+ fixtureName ,
328+ fixtureName ,
329+ Collections .emptyList ()
330+ ).setStatus (getStatus (throwable ).orElse (Status .BROKEN ));
331+ configurationResult .getLabels ().add (
332+ new Label ().setName (ALLURE_ID_LABEL_NAME ).setValue ("-1" )
333+ );
334+ copyFailureDetails (failureDetails , configurationResult .getStatusDetails ());
335+ writeCompletedTest (containerUuid , configurationResult , fixtureSpec );
336+
337+ fixtureSpec .getBottomSpec ().getAllFeaturesInExecutionOrder ().stream ()
338+ .filter (feature -> !feature .isExcluded ())
339+ .filter (feature -> !feature .isSkipped ())
340+ .forEach (feature -> {
341+ final TestResult skippedResult = createTestResult (
342+ feature ,
343+ feature .getName (),
344+ feature .getDisplayName (),
345+ Collections .emptyList ()
346+ ).setStatus (Status .SKIPPED );
347+ copyFailureDetails (failureDetails , skippedResult .getStatusDetails ());
348+ final String failureMessage = skippedResult .getStatusDetails ().getMessage ();
349+ skippedResult .getStatusDetails ().setMessage (
350+ Objects .isNull (failureMessage )
351+ ? "Skipped because setup spec failed"
352+ : "Skipped because setup spec failed: " + failureMessage
353+ );
354+ writeCompletedTest (containerUuid , skippedResult , feature .getSpec ());
355+ });
285356 }
286357
287- private String getQualifiedName (final IterationInfo iteration ) {
288- return this .getQualifiedName (iteration .getFeature ().getSpec ().getReflection ().getName (), iteration .getName ());
358+ private void copyFailureDetails (final StatusDetails source , final StatusDetails target ) {
359+ if (Objects .nonNull (source )) {
360+ target .setMessage (source .getMessage ())
361+ .setTrace (source .getTrace ())
362+ .setActual (source .getActual ())
363+ .setExpected (source .getExpected ());
364+ }
365+ }
366+
367+ private void writeCompletedTest (final String containerUuid ,
368+ final TestResult result ,
369+ final SpecInfo specInfo ) {
370+ final AllureExternalKey resultKey = testKey (result .getUuid ());
371+ getLifecycle ().scheduleTest (
372+ Collections .singletonList (scopeKey (containerUuid )),
373+ resultKey ,
374+ result
375+ );
376+ getLifecycle ().addDefaultLabels (resultKey , getDefaultLabels (specInfo ));
377+ getLifecycle ().startTest (resultKey );
378+ getLifecycle ().stopTest (resultKey );
379+ getLifecycle ().writeTest (resultKey );
289380 }
290381
291382 private String getQualifiedName (final FeatureInfo featureInfo ) {
@@ -645,6 +736,7 @@ public void intercept(final IMethodInvocation invocation) throws Throwable {
645736 );
646737 }
647738
739+ Throwable failure = null ;
648740 try {
649741 invocation .proceed ();
650742 getLifecycle ().updateFixture (
@@ -658,10 +750,17 @@ public void intercept(final IMethodInvocation invocation) throws Throwable {
658750 .setStatus (getStatus (throwable ).orElse (Status .BROKEN ))
659751 .setStatusDetails (getStatusDetails (throwable ).orElse (null ))
660752 );
661- throw ExceptionUtils . sneakyThrow ( throwable ) ;
753+ failure = throwable ;
662754 } finally {
663755 getLifecycle ().stopFixture (fixtureKey );
664756 }
757+
758+ if (Objects .nonNull (failure )) {
759+ if (MethodKind .SETUP_SPEC .equals (kind )) {
760+ reportSetupSpecFailure (invocation , containerUuid , fixtureName , failure );
761+ }
762+ throw ExceptionUtils .sneakyThrow (failure );
763+ }
665764 }
666765
667766 }
0 commit comments