Skip to content

Commit 30d0913

Browse files
authored
Use globals for errors produced throuth RunAborted event (#1442)
1 parent e698842 commit 30d0913

5 files changed

Lines changed: 234 additions & 2 deletions

File tree

‎allure-scalatest/src/main/scala/io/qameta/allure/scalatest/AllureScalatest.scala‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class AllureScalatest(val lifecycle: AllureLifecycle) extends Reporter {
9191
case event: SuiteStarting => startSuite(event)
9292
case event: SuiteCompleted => completeSuite(event)
9393
case event: SuiteAborted => abortSuite(event)
94+
case event: RunAborted => abortRun(event)
9495
case event: TestStarting => startTest(event)
9596
case event: TestFailed => failTestCase(event)
9697
case event: TestCanceled => cancelTestCase(event)
@@ -110,6 +111,16 @@ class AllureScalatest(val lifecycle: AllureLifecycle) extends Reporter {
110111

111112
def abortSuite(event: SuiteAborted): Unit = {
112113
removeSuiteLocation(event.suiteId)
114+
reportAbort(s"ScalaTest suite ${event.suiteName} (${event.suiteId}) aborted", event.message, event.throwable)
115+
}
116+
117+
def abortRun(event: RunAborted): Unit = {
118+
reportAbort("ScalaTest run aborted", event.message, event.throwable)
119+
}
120+
121+
private def reportAbort(context: String, message: String, throwable: Option[Throwable]): Unit = {
122+
val errorContext = if (throwable.exists(_.getMessage == message)) context else s"$context: $message"
123+
lifecycle.writeGlobals(createGlobalError(errorContext, throwable.orNull))
113124
}
114125

115126
def startTest(event: TestStarting): Unit = {

‎allure-scalatest/src/test/scala/io/qameta/allure/scalatest/AllureScalatestTest.scala‎

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import io.qameta.allure.model.{Stage, Status}
2020
import io.qameta.allure.scalatest.testdata._
2121
import io.qameta.allure.test.IsolatedLifecycle
2222
import io.qameta.allure.test.{AllureResults, AllureResultsWriterStub, RunUtils}
23-
import io.qameta.allure.test.AllureTestCommonsUtils.expectedHistoryId
23+
import io.qameta.allure.test.AllureTestCommonsUtils.{attach, expectedHistoryId}
2424
import io.qameta.allure.util.ResultsUtils.createParameter
25-
import io.qameta.allure.{Allure, AllureLifecycle}
25+
import io.qameta.allure.{Allure, AllureLifecycle, Description}
2626
import org.junit.jupiter.api.Test
27+
import org.opentest4j.AssertionFailedError
28+
import org.scalatest.events.{Event, Ordinal, RunAborted, SuiteAborted}
2729
import org.scalatest.matchers.should.Matchers._
2830
import org.scalatest.tools.Runner
2931

@@ -86,6 +88,7 @@ class AllureScalatestTest {
8688
.map(item => item.getStatus)
8789

8890
every(statuses) shouldBe Status.PASSED
91+
results.getGlobals shouldBe empty
8992
}
9093

9194
@Test
@@ -98,6 +101,7 @@ class AllureScalatestTest {
98101
.map(item => item.getStatus)
99102

100103
every(statuses) shouldBe Status.FAILED
104+
results.getGlobals shouldBe empty
101105
}
102106

103107
@Test
@@ -111,6 +115,7 @@ class AllureScalatestTest {
111115
.toList
112116

113117
every(statuses) should be(Status.BROKEN)
118+
results.getGlobals shouldBe empty
114119
}
115120

116121
@Test
@@ -124,6 +129,7 @@ class AllureScalatestTest {
124129
.toList
125130

126131
every(statuses) should be(Status.SKIPPED)
132+
results.getGlobals shouldBe empty
127133
}
128134

129135
@Test
@@ -233,6 +239,131 @@ class AllureScalatestTest {
233239
}
234240
}
235241

242+
@Test
243+
@Description("A failed beforeAll is reported as a global error with its suite context and exception details.")
244+
def shouldReportBeforeAllFailureAsGlobalError(): Unit = {
245+
val started = System.currentTimeMillis()
246+
val results = run(classOf[BeforeAllFailureSpec])
247+
248+
results.getGlobals should have length 1
249+
val errors = results.getGlobals.asScala.flatMap(_.getErrors.asScala)
250+
errors should have length 1
251+
val error = errors.head
252+
error.getMessage should include("BeforeAllFailureSpec")
253+
error.getMessage should include("beforeAll failed")
254+
error.getTrace should include("java.lang.IllegalStateException: beforeAll failed")
255+
error.getTrace should include("BeforeAllFailureSpec.beforeAll")
256+
error.getTimestamp should be >= started
257+
error.getTimestamp should be <= System.currentTimeMillis()
258+
}
259+
260+
@Test
261+
@Description("A failed afterAll is reported as a global error with its suite context and exception details.")
262+
def shouldReportAfterAllFailureAsGlobalError(): Unit = {
263+
val results = run(classOf[AfterAllFailureSpec])
264+
265+
results.getGlobals should have length 1
266+
val errors = results.getGlobals.asScala.flatMap(_.getErrors.asScala)
267+
errors should have length 1
268+
val error = errors.head
269+
error.getMessage should include("AfterAllFailureSpec")
270+
error.getMessage should include("afterAll failed")
271+
error.getTrace should include("java.lang.IllegalStateException: afterAll failed")
272+
error.getTrace should include("AfterAllFailureSpec.afterAll")
273+
}
274+
275+
@Test
276+
@Description("Suite construction failures are reported as run-level global errors.")
277+
def shouldReportSuiteConstructionFailureAsGlobalError(): Unit = {
278+
val results = run(classOf[ConstructorFailureSpec])
279+
280+
results.getGlobals should have length 1
281+
val errors = results.getGlobals.asScala.flatMap(_.getErrors.asScala)
282+
errors should have length 1
283+
val error = errors.head
284+
error.getMessage should include("ScalaTest run")
285+
error.getTrace should include("ConstructorFailureSpec")
286+
error.getTrace should include("suite construction failed")
287+
}
288+
289+
@Test
290+
@Description("A suite abort with no throwable is reported with its message on the supplied lifecycle.")
291+
def shouldReportSuiteAbortWithoutThrowable(): Unit = {
292+
val results = report(
293+
SuiteAborted(
294+
ordinal = new Ordinal(0),
295+
message = "suite initialization was interrupted",
296+
suiteName = "fixture suite",
297+
suiteId = "fixture-suite",
298+
suiteClassName = None
299+
)
300+
)
301+
302+
results.getGlobals should have length 1
303+
val errors = results.getGlobals.asScala.flatMap(_.getErrors.asScala)
304+
errors should have length 1
305+
val error = errors.head
306+
error.getMessage should include("fixture suite")
307+
error.getMessage should include("suite initialization was interrupted")
308+
error.getTrace shouldBe null
309+
error.getTimestamp should be > 0L
310+
}
311+
312+
@Test
313+
@Description("A run abort with no throwable is reported with its message on the supplied lifecycle.")
314+
def shouldReportRunAbortWithoutThrowable(): Unit = {
315+
val results = report(
316+
RunAborted(
317+
ordinal = new Ordinal(0),
318+
message = "test discovery was interrupted",
319+
throwable = None
320+
)
321+
)
322+
323+
results.getGlobals should have length 1
324+
val errors = results.getGlobals.asScala.flatMap(_.getErrors.asScala)
325+
errors should have length 1
326+
val error = errors.head
327+
error.getMessage should include("ScalaTest run")
328+
error.getMessage should include("test discovery was interrupted")
329+
error.getTrace shouldBe null
330+
error.getTimestamp should be > 0L
331+
}
332+
333+
@Test
334+
@Description("A run abort is reported with its event context, exception details, and comparison values.")
335+
def shouldReportRunAbortComparisonDetails(): Unit = {
336+
val cause = new AssertionFailedError("global comparison failed", "expected value", "actual value")
337+
val results = report(
338+
RunAborted(
339+
ordinal = new Ordinal(0),
340+
message = "ScalaTest initialization failed",
341+
throwable = Some(cause)
342+
)
343+
)
344+
345+
results.getGlobals should have length 1
346+
val errors = results.getGlobals.asScala.flatMap(_.getErrors.asScala)
347+
errors should have length 1
348+
val error = errors.head
349+
error.getMessage should include("ScalaTest initialization failed")
350+
error.getMessage should include("global comparison failed")
351+
error.getTrace should include("org.opentest4j.AssertionFailedError: global comparison failed")
352+
error.getExpected shouldBe cause.getExpected.toString
353+
error.getActual shouldBe cause.getActual.toString
354+
}
355+
356+
private def report(event: Event): AllureResults = {
357+
val results = new AllureResultsWriterStub()
358+
val reporter = new AllureScalatest(new AllureLifecycle(results))
359+
try {
360+
reporter(event)
361+
} finally {
362+
attach(results)
363+
}
364+
results
365+
}
366+
236367
private def run(clazz: Class[_]): AllureResults = {
237368
RunUtils.runTests { _ =>
238369
val args = new ListBuffer[String]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2016-2026 Qameta Software Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure.scalatest.testdata
17+
18+
import org.scalatest.BeforeAndAfterAll
19+
import org.scalatest.flatspec.AnyFlatSpec
20+
21+
class AfterAllFailureSpec extends AnyFlatSpec with BeforeAndAfterAll {
22+
23+
override protected def afterAll(): Unit = {
24+
super.afterAll()
25+
throw new IllegalStateException("afterAll failed")
26+
}
27+
28+
"test" should "pass before cleanup" in {}
29+
30+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2016-2026 Qameta Software Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure.scalatest.testdata
17+
18+
import org.scalatest.BeforeAndAfterAll
19+
import org.scalatest.flatspec.AnyFlatSpec
20+
21+
class BeforeAllFailureSpec extends AnyFlatSpec with BeforeAndAfterAll {
22+
23+
override protected def beforeAll(): Unit = {
24+
super.beforeAll()
25+
throw new IllegalStateException("beforeAll failed")
26+
}
27+
28+
"test" should "not run" in {
29+
fail("test body must not run")
30+
}
31+
32+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2016-2026 Qameta Software Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure.scalatest.testdata
17+
18+
import org.scalatest.flatspec.AnyFlatSpec
19+
20+
class ConstructorFailureSpec extends AnyFlatSpec {
21+
22+
"test" should "not run" in {
23+
fail("test body must not run")
24+
}
25+
26+
throw new IllegalStateException("suite construction failed")
27+
28+
}

0 commit comments

Comments
 (0)