Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ public class AllureJunitPlatform implements TestExecutionListener {

private static final boolean HAS_CUCUMBERJVM7_IN_CLASSPATH = isClassAvailableOnClasspath("io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm");

private static final boolean HAS_KARATE_IN_CLASSPATH = isClassAvailableOnClasspath("io.qameta.allure.karate.AllureKarate");

private static final String ENGINE_SPOCK2 = "spock";
private static final String ENGINE_CUCUMBER = "cucumber";
private static final String KARATE_JUNIT5_TEST = "com.intuit.karate.junit5.Karate$Test";
private static final String KARATE_JUNIT6_TEST = "io.karatelabs.junit6.Karate$Test";

private final ThreadLocal<TestPlan> testPlanStorage = new InheritableThreadLocal<>();

Expand Down Expand Up @@ -217,7 +221,25 @@ private boolean shouldSkipReportingFor(final TestIdentifier testIdentifier) {
final String engine = maybeEngine.get();

return HAS_SPOCK2_IN_CLASSPATH && ENGINE_SPOCK2.equals(engine)
|| HAS_CUCUMBERJVM7_IN_CLASSPATH && ENGINE_CUCUMBER.equals(engine);
|| HAS_CUCUMBERJVM7_IN_CLASSPATH && ENGINE_CUCUMBER.equals(engine)
|| HAS_KARATE_IN_CLASSPATH && isKarateTest(testIdentifier);
}

private boolean isKarateTest(final TestIdentifier testIdentifier) {
return getParents(testIdentifier).stream()
.map(TestIdentifier::getSource)
.filter(Optional::isPresent)
.map(Optional::get)
.map(AllureJunitPlatformUtils::getTestMethod)
.filter(Optional::isPresent)
.map(Optional::get)
.flatMap(method -> Stream.of(method.getAnnotations()))
.map(Annotation::annotationType)
.map(Class::getName)
.anyMatch(
annotation -> KARATE_JUNIT5_TEST.equals(annotation)
|| KARATE_JUNIT6_TEST.equals(annotation)
);
}

private Optional<String> getEngine(final TestIdentifier testIdentifier) {
Expand All @@ -240,7 +262,7 @@ private static boolean isClassAvailableOnClasspath(final String clazz) {
try {
AllureJunitPlatform.class.getClassLoader().loadClass(clazz);
return true;
} catch (Exception ignored) {
} catch (LinkageError | Exception ignored) {
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 com.intuit.karate.junit5;

import org.junit.jupiter.api.DynamicContainer;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Iterator;
import java.util.List;

/**
* Minimal test fixture that reproduces Karate's JUnit dynamic-node shape without introducing a Java 21 dependency.
*/
public final class Karate implements Iterable<DynamicNode> {

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@TestFactory
public @interface Test {
}

private Karate() {
}

public static Karate run() {
return new Karate();
}

@Override
public Iterator<DynamicNode> iterator() {
final DynamicTest scenario = DynamicTest.dynamicTest("[1:3] My scenario", () -> {
});
final DynamicContainer feature = DynamicContainer.dynamicContainer("Testing web page", List.of(scenario));
return List.<DynamicNode>of(feature).iterator();
}
}
Original file line number Diff line number Diff line change
@@ -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.karatelabs.core;

/**
* Test-only stand-in for the optional Karate listener contract.
*/
public interface RunListener {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.karatelabs.junit6;

import org.junit.jupiter.api.DynamicContainer;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Iterator;
import java.util.List;

/**
* Minimal test fixture for the current Karate JUnit package.
*/
public final class Karate implements Iterable<DynamicNode> {

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@TestFactory
public @interface Test {
}

private Karate() {
}

public static Karate run() {
return new Karate();
}

@Override
public Iterator<DynamicNode> iterator() {
final DynamicTest scenario = DynamicTest.dynamicTest("[1:3] Current scenario", () -> {
});
final DynamicContainer feature = DynamicContainer.dynamicContainer("Current web page", List.of(scenario));
return List.<DynamicNode>of(feature).iterator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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.junitplatform;

import io.qameta.allure.Description;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThatCode;

class AllureJunitPlatformClasspathTest {

private static final String KARATE_ADAPTER = "io.qameta.allure.karate.AllureKarate";
private static final String KARATE_RUN_LISTENER = "io.karatelabs.core.RunListener";
private static final Set<String> CHILD_FIRST_CLASSES = Set.of(
AllureJunitPlatform.class.getName(),
KARATE_ADAPTER
);

/**
* Keeps JUnit Platform reporting available when an optional adapter links to a missing framework version.
*/
@Test
@Description
void shouldIgnoreMissingOptionalFrameworkDependencyDuringClasspathProbe() {
final ClassLoader classLoader = new MissingOptionalDependencyClassLoader(
AllureJunitPlatformClasspathTest.class.getClassLoader()
);

assertThatCode(() -> Class.forName(AllureJunitPlatform.class.getName(), true, classLoader))
.doesNotThrowAnyException();
}

private static final class MissingOptionalDependencyClassLoader extends ClassLoader {

MissingOptionalDependencyClassLoader(final ClassLoader parent) {
super(parent);
}

@Override
protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
if (KARATE_RUN_LISTENER.equals(name)) {
throw new ClassNotFoundException(name);
}
if (!CHILD_FIRST_CLASSES.contains(name)) {
return super.loadClass(name, resolve);
}
synchronized (getClassLoadingLock(name)) {
final Class<?> loadedClass = findLoadedClass(name);
final Class<?> result = loadedClass == null ? defineClassFromParent(name) : loadedClass;
if (resolve) {
resolveClass(result);
}
return result;
}
}

private Class<?> defineClassFromParent(final String name) throws ClassNotFoundException {
final String resourceName = name.replace('.', '/') + ".class";
try (InputStream input = getParent().getResourceAsStream(resourceName)) {
if (input == null) {
throw new ClassNotFoundException(name);
}
final byte[] bytes = input.readAllBytes();
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.qameta.allure.junitplatform;

import io.github.glytching.junit.extension.system.SystemProperty;
import io.qameta.allure.Description;
import io.qameta.allure.Issue;
import io.qameta.allure.junitplatform.features.ActualExpectedStatusDetailsTests;
import io.qameta.allure.junitplatform.features.AllureIdAnnotationSupport;
Expand All @@ -29,6 +30,7 @@
import io.qameta.allure.junitplatform.features.FailedTests;
import io.qameta.allure.junitplatform.features.FlakyMutedTest;
import io.qameta.allure.junitplatform.features.JupiterUniqueIdTest;
import io.qameta.allure.junitplatform.features.KarateTests;
import io.qameta.allure.junitplatform.features.MarkerAnnotationSupport;
import io.qameta.allure.junitplatform.features.MetaAnnotationTest;
import io.qameta.allure.junitplatform.features.NestedDisplayNameTests;
Expand Down Expand Up @@ -377,6 +379,21 @@ void shouldProcessDynamicTests() {
.containsExactlyInAnyOrder("testA", "testB", "testC");
}

/**
* Ensures the dedicated Karate adapter remains the only reporter for Karate scenarios while ordinary Jupiter
* tests from the same class are still reported by the JUnit Platform integration.
*/
@Test
@Issue("1127")
@Description
void shouldSkipKarateDynamicTestsWhenAllureKarateIsPresent() {
final AllureResults results = runClasses(KarateTests.class);

assertThat(results.getTestResults())
.extracting(TestResult::getName)
.containsExactly("ordinaryJupiterTest()");
}

@Test
@AllureFeatures.Parameters
void shouldProcessParametrisedTests() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.junitplatform.features;

import com.intuit.karate.junit5.Karate;
import org.junit.jupiter.api.Test;

public class KarateTests {

@Karate.Test
Karate karateScenarios() {
return Karate.run();
}

@io.karatelabs.junit6.Karate.Test
io.karatelabs.junit6.Karate currentKarateScenarios() {
return io.karatelabs.junit6.Karate.run();
}

@Test
void ordinaryJupiterTest() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.karate;

import io.karatelabs.core.RunListener;

/**
* Classpath marker that represents the dedicated Karate adapter in JUnit Platform integration tests.
*/
public final class AllureKarate implements RunListener {

private AllureKarate() {
}
}
Loading
Loading