diff --git a/.gitignore b/.gitignore index b85107a90..fc9f1bb0d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ build out .gradletasknamecache +#AspectJ crash dumps +ajcore.*.txt + #IDEA Files .idea/* !.idea/vcs.xml diff --git a/allure-assertj/build.gradle.kts b/allure-assertj/build.gradle.kts index 10005f9eb..0e32e5b3b 100644 --- a/allure-assertj/build.gradle.kts +++ b/allure-assertj/build.gradle.kts @@ -1,12 +1,17 @@ description = "Allure AssertJ Integration" +// Compile the optional type used by the regression fixture without adding it to the test runtime. +val missingDependency by sourceSets.creating + dependencies { api(project(":allure-java-commons")) compileOnly("org.aspectj:aspectjrt") compileOnly("org.assertj:assertj-core") + testImplementation("org.aspectj:aspectjweaver") testImplementation("org.assertj:assertj-core") testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.slf4j:slf4j-simple") + testCompileOnly(missingDependency.output) testImplementation(project(":allure-java-commons-test")) testImplementation(project(":allure-junit-platform")) testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java index 351b6c205..96128ea92 100644 --- a/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java @@ -44,10 +44,10 @@ public class AllureAspectJ { @Pointcut( "(" - + "call(public static * org.assertj.core.api.Assertions*.assertThat*(..))" - + " || call(public static * org.assertj.core.api.BDDAssertions*.then*(..))" - + " || call(public * org.assertj.core.api.*SoftAssertionsProvider+.assertThat*(..))" - + " || call(public * org.assertj.core.api.*SoftAssertionsProvider+.then*(..))" + + "execution(public static * org.assertj.core.api.Assertions*.assertThat*(..))" + + " || execution(public static * org.assertj.core.api.BDDAssertions*.then*(..))" + + " || execution(public * org.assertj.core.api.*SoftAssertionsProvider+.assertThat*(..))" + + " || execution(public * org.assertj.core.api.*SoftAssertionsProvider+.then*(..))" + ")" ) @@ -60,11 +60,12 @@ public void assertFactoryCall() { @Pointcut( "(" - + "call(public * org.assertj.core.api.AbstractAssert+.*(..))" - + " || call(public * org.assertj.core.api.Assert+.*(..))" - + " || call(public * org.assertj.core.api.Descriptable+.*(..))" + + "execution(public * org.assertj.core.api.AbstractAssert+.*(..))" + + " || execution(public * org.assertj.core.api.Assert+.*(..))" + + " || execution(public * org.assertj.core.api.Descriptable+.*(..))" + ")" + " && target(assertion)" + + " && !execution(* org.assertj.core.api.AssertJProxySetup.*(..))" ) /** @@ -76,16 +77,8 @@ public void assertOperationCall(final AbstractAssert assertion) { //pointcut body, should be empty } - /** - * Handles the user code call callback. - */ - @Pointcut("!within(org.assertj..*) && !within(io.qameta.allure.assertj.AllureAspectJ)") - public void userCodeCall() { - //pointcut body, should be empty - } - @AfterReturning( - pointcut = "assertFactoryCall() && userCodeCall()", + pointcut = "assertFactoryCall()", returning = "result" ) @@ -112,7 +105,7 @@ public void logAssertCreation(final JoinPoint joinPoint, final Object result) { * @return the log assert operation * @throws Throwable if the underlying framework operation fails */ - @Around("assertOperationCall(assertion) && userCodeCall()") + @Around("assertOperationCall(assertion)") public Object logAssertOperation(final ProceedingJoinPoint joinPoint, final AbstractAssert assertion) throws Throwable { diff --git a/allure-assertj/src/missingDependency/java/io/qameta/allure/assertj/fixture/MissingOptionalType.java b/allure-assertj/src/missingDependency/java/io/qameta/allure/assertj/fixture/MissingOptionalType.java new file mode 100644 index 000000000..ddd4209c5 --- /dev/null +++ b/allure-assertj/src/missingDependency/java/io/qameta/allure/assertj/fixture/MissingOptionalType.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.assertj.fixture; + +public interface MissingOptionalType extends Runnable { + + String getValue(); + +} diff --git a/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java b/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java index 2f1410be0..6133fddd7 100644 --- a/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java +++ b/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java @@ -15,6 +15,7 @@ */ package io.qameta.allure.assertj; +import io.qameta.allure.assertj.fixture.MissingOptionalTypeFixture; import io.qameta.allure.model.Parameter; import io.qameta.allure.model.Status; import io.qameta.allure.model.StatusDetails; @@ -23,6 +24,7 @@ import io.qameta.allure.test.AllureFeatures; import io.qameta.allure.test.AllureResults; import io.qameta.allure.test.IsolatedLifecycle; +import org.aspectj.weaver.Dump; import org.assertj.core.api.AbstractStringAssert; import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.SoftAssertions; @@ -36,10 +38,36 @@ import static io.qameta.allure.test.RunUtils.runWithinTestContext; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.tuple; @IsolatedLifecycle class AllureAspectJTest { + @Test + void shouldNotInspectClassesWithMissingOptionalTypes() { + final ClassLoader classLoader = MissingOptionalTypeFixture.class.getClassLoader(); + assertThatExceptionOfType(ClassNotFoundException.class) + .isThrownBy( + () -> Class.forName( + MissingOptionalTypeFixture.MISSING_TYPE_NAME, + false, + classLoader + ) + ); + + final String dumpBefore = Dump.getLastDumpFileName(); + + final Class fixtureType = MissingOptionalTypeFixture.loadClassWithMissingTypeSignature(); + final String dumpAfter = Dump.getLastDumpFileName(); + + assertThatExceptionOfType(TypeNotPresentException.class) + .isThrownBy(fixtureType::getGenericSuperclass) + .withMessageContaining(MissingOptionalTypeFixture.MISSING_TYPE_NAME); + assertThat(dumpAfter) + .as("last AspectJ dump file") + .isEqualTo(dumpBefore); + } + @AllureFeatures.Steps @Test void shouldCreateSemanticChainForScalarAssert() { diff --git a/allure-assertj/src/test/java/io/qameta/allure/assertj/fixture/MissingOptionalTypeFixture.java b/allure-assertj/src/test/java/io/qameta/allure/assertj/fixture/MissingOptionalTypeFixture.java new file mode 100644 index 000000000..a39a9c164 --- /dev/null +++ b/allure-assertj/src/test/java/io/qameta/allure/assertj/fixture/MissingOptionalTypeFixture.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.assertj.fixture; + +import java.util.function.Function; + +/** + * Loads a bounded generic subclass whose type argument is deliberately absent at runtime. + */ +public final class MissingOptionalTypeFixture { + + public static final String MISSING_TYPE_NAME = "io.qameta.allure.assertj.fixture.MissingOptionalType"; + + private MissingOptionalTypeFixture() { + } + + public static Class loadClassWithMissingTypeSignature() { + return MissingTypeProperties.class; + } + + private interface TypeProperties { + + Class getType(); + + String get(T instance); + + } + + private static class MappedProperties implements TypeProperties { + + @Override + public Class getType() { + return null; + } + + protected void add(final Function getter) { + //fixture method, should be empty + } + + @Override + public String get(final T instance) { + return null; + } + + } + + private static final class MissingTypeProperties extends MappedProperties { + + @Override + public Class getType() { + return null; + } + + private MissingTypeProperties() { + add(MissingOptionalType::getValue); + } + + } + +} diff --git a/allure-jupiter-assert/build.gradle.kts b/allure-jupiter-assert/build.gradle.kts index 36773eecd..fe63eb8d3 100644 --- a/allure-jupiter-assert/build.gradle.kts +++ b/allure-jupiter-assert/build.gradle.kts @@ -1,13 +1,18 @@ description = "Allure Jupiter Assertions Integration" +// Compile the optional type used by the regression fixture without adding it to the test runtime. +val missingDependency by sourceSets.creating + dependencies { api(project(":allure-jupiter")) compileOnly("org.aspectj:aspectjrt") compileOnly("org.junit.jupiter:junit-jupiter-api") + testImplementation("org.aspectj:aspectjweaver") testImplementation("org.assertj:assertj-core") testImplementation(project(":allure-assertj")) testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.slf4j:slf4j-simple") + testCompileOnly(missingDependency.output) testImplementation(project(":allure-java-commons-test")) testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/allure-jupiter-assert/src/main/java/io/qameta/allure/jupiterassert/AllureJupiterAssert.java b/allure-jupiter-assert/src/main/java/io/qameta/allure/jupiterassert/AllureJupiterAssert.java index 7f0d487cd..d224eb3a1 100644 --- a/allure-jupiter-assert/src/main/java/io/qameta/allure/jupiterassert/AllureJupiterAssert.java +++ b/allure-jupiter-assert/src/main/java/io/qameta/allure/jupiterassert/AllureJupiterAssert.java @@ -50,7 +50,7 @@ public class AllureJupiterAssert { /** * Handles the any assert callback. */ - @Pointcut("call(void org.junit.jupiter.api.Assertions.*(..)) || throwable()") + @Pointcut("execution(void org.junit.jupiter.api.Assertions.*(..)) || throwable()") public void anyAssert() { } @@ -58,7 +58,7 @@ public void anyAssert() { /** * Handles the throwable callback. */ - @Pointcut("call(Throwable org.junit.jupiter.api.Assertions.*(..))") + @Pointcut("execution(Throwable org.junit.jupiter.api.Assertions.*(..))") public void throwable() { } diff --git a/allure-jupiter-assert/src/missingDependency/java/io/qameta/allure/jupiterassert/fixture/MissingOptionalType.java b/allure-jupiter-assert/src/missingDependency/java/io/qameta/allure/jupiterassert/fixture/MissingOptionalType.java new file mode 100644 index 000000000..105660f00 --- /dev/null +++ b/allure-jupiter-assert/src/missingDependency/java/io/qameta/allure/jupiterassert/fixture/MissingOptionalType.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.jupiterassert.fixture; + +public interface MissingOptionalType extends Runnable { + + String getValue(); + +} diff --git a/allure-jupiter-assert/src/test/java/io/qameta/allure/jupiterassert/AllureJupiterAssertTest.java b/allure-jupiter-assert/src/test/java/io/qameta/allure/jupiterassert/AllureJupiterAssertTest.java index efa2421fb..ef193aa4b 100644 --- a/allure-jupiter-assert/src/test/java/io/qameta/allure/jupiterassert/AllureJupiterAssertTest.java +++ b/allure-jupiter-assert/src/test/java/io/qameta/allure/jupiterassert/AllureJupiterAssertTest.java @@ -16,19 +16,49 @@ package io.qameta.allure.jupiterassert; import io.qameta.allure.Allure; +import io.qameta.allure.jupiterassert.fixture.MissingOptionalTypeFixture; +import io.qameta.allure.model.Status; import io.qameta.allure.model.StepResult; import io.qameta.allure.model.TestResult; import io.qameta.allure.test.AllureResults; import io.qameta.allure.test.IsolatedLifecycle; +import org.aspectj.weaver.Dump; import org.junit.jupiter.api.Test; import static io.qameta.allure.test.RunUtils.runWithinTestContext; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; @IsolatedLifecycle class AllureJupiterAssertTest { + @Test + void shouldNotInspectClassesWithMissingOptionalTypes() { + final ClassLoader classLoader = MissingOptionalTypeFixture.class.getClassLoader(); + assertThatExceptionOfType(ClassNotFoundException.class) + .isThrownBy( + () -> Class.forName( + MissingOptionalTypeFixture.MISSING_TYPE_NAME, + false, + classLoader + ) + ); + + final String dumpBefore = Dump.getLastDumpFileName(); + + final Class fixtureType = MissingOptionalTypeFixture.loadClassWithMissingTypeSignature(); + final String dumpAfter = Dump.getLastDumpFileName(); + + assertThatExceptionOfType(TypeNotPresentException.class) + .isThrownBy(fixtureType::getGenericSuperclass) + .withMessageContaining(MissingOptionalTypeFixture.MISSING_TYPE_NAME); + assertThat(dumpAfter) + .as("last AspectJ dump file") + .isEqualTo(dumpBefore); + } + @Test void shouldHandleAssertEquals() { final AllureResults results = Allure.step( @@ -43,4 +73,26 @@ void shouldHandleAssertEquals() { .extracting(StepResult::getName) .containsExactly("assert 'expectedString' Equals 'actualString'"); } + + @Test + void shouldHandleAssertThrows() { + final AllureResults results = Allure.step( + "Execute throwable-returning JUnit assertion with Allure Jupiter assert lifecycle", + () -> runWithinTestContext( + () -> assertThrows(IllegalStateException.class, () -> { + throw new IllegalStateException("expected"); + }) + ) + ); + + assertThat(results.getTestResults()) + .flatExtracting(TestResult::getSteps) + .singleElement() + .satisfies(step -> { + assertThat(step.getName()) + .startsWith("assert 'class java.lang.IllegalStateException' Throws '"); + assertThat(step.getStatus()) + .isEqualTo(Status.PASSED); + }); + } } diff --git a/allure-jupiter-assert/src/test/java/io/qameta/allure/jupiterassert/fixture/MissingOptionalTypeFixture.java b/allure-jupiter-assert/src/test/java/io/qameta/allure/jupiterassert/fixture/MissingOptionalTypeFixture.java new file mode 100644 index 000000000..6008ba628 --- /dev/null +++ b/allure-jupiter-assert/src/test/java/io/qameta/allure/jupiterassert/fixture/MissingOptionalTypeFixture.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.jupiterassert.fixture; + +import java.util.function.Function; + +/** + * Loads a bounded generic subclass whose type argument is deliberately absent at runtime. + */ +public final class MissingOptionalTypeFixture { + + public static final String MISSING_TYPE_NAME = "io.qameta.allure.jupiterassert.fixture.MissingOptionalType"; + + private MissingOptionalTypeFixture() { + } + + public static Class loadClassWithMissingTypeSignature() { + return MissingTypeProperties.class; + } + + private interface TypeProperties { + + Class getType(); + + String get(T instance); + + } + + private static class MappedProperties implements TypeProperties { + + @Override + public Class getType() { + return null; + } + + protected void add(final Function getter) { + //fixture method, should be empty + } + + @Override + public String get(final T instance) { + return null; + } + + } + + private static final class MissingTypeProperties extends MappedProperties { + + @Override + public Class getType() { + return null; + } + + private MissingTypeProperties() { + add(MissingOptionalType::getValue); + } + + } + +}