diff --git a/metadata/index.json b/metadata/index.json index 05ddffc6b..1b1785074 100644 --- a/metadata/index.json +++ b/metadata/index.json @@ -369,6 +369,10 @@ "allowed-packages" : [ "org.quartz" ], "directory" : "org.quartz-scheduler/quartz", "module" : "org.quartz-scheduler:quartz" +}, { + "allowed-packages" : [ "org.slf4j" ], + "directory" : "org.slf4j/slf4j-api", + "module" : "org.slf4j:slf4j-api" }, { "allowed-packages" : [ "org.testcontainers" ], "directory" : "org.testcontainers/testcontainers", diff --git a/metadata/org.slf4j/slf4j-api/1.7.36/index.json b/metadata/org.slf4j/slf4j-api/1.7.36/index.json new file mode 100644 index 000000000..6087e7795 --- /dev/null +++ b/metadata/org.slf4j/slf4j-api/1.7.36/index.json @@ -0,0 +1,5 @@ +[ + "reflect-config.json", + "resource-config.json", + "serialization-config.json" +] diff --git a/metadata/org.slf4j/slf4j-api/1.7.36/reflect-config.json b/metadata/org.slf4j/slf4j-api/1.7.36/reflect-config.json new file mode 100644 index 000000000..930738bcb --- /dev/null +++ b/metadata/org.slf4j/slf4j-api/1.7.36/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "condition": { + "typeReachable": "org.slf4j.impl.StaticLoggerBinder" + }, + "name": "java.util.concurrent.atomic.AtomicReference", + "fields": [ + { + "name": "value" + } + ] + } +] \ No newline at end of file diff --git a/metadata/org.slf4j/slf4j-api/1.7.36/resource-config.json b/metadata/org.slf4j/slf4j-api/1.7.36/resource-config.json new file mode 100644 index 000000000..a5844ef1f --- /dev/null +++ b/metadata/org.slf4j/slf4j-api/1.7.36/resource-config.json @@ -0,0 +1,8 @@ +{ + "resources":{ + "includes":[{ + "condition":{"typeReachable":"org.slf4j.LoggerFactory"}, + "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }]}, + "bundles":[] +} \ No newline at end of file diff --git a/metadata/org.slf4j/slf4j-api/1.7.36/serialization-config.json b/metadata/org.slf4j/slf4j-api/1.7.36/serialization-config.json new file mode 100644 index 000000000..80da8c9c4 --- /dev/null +++ b/metadata/org.slf4j/slf4j-api/1.7.36/serialization-config.json @@ -0,0 +1,17 @@ +{ + "types": [ + { + "name": "org.slf4j.helpers.BasicMarker" + }, + { + "name": "java.lang.String" + }, + { + "name": "java.util.concurrent.CopyOnWriteArrayList" + } + ], + "lambdaCapturingTypes": [ + ], + "proxies": [ + ] +} \ No newline at end of file diff --git a/metadata/org.slf4j/slf4j-api/index.json b/metadata/org.slf4j/slf4j-api/index.json new file mode 100644 index 000000000..e042ea05e --- /dev/null +++ b/metadata/org.slf4j/slf4j-api/index.json @@ -0,0 +1,10 @@ +[ + { + "latest": true, + "metadata-version": "1.7.36", + "module": "org.slf4j:slf4j-api", + "tested-versions": [ + "1.7.36" + ] + } +] diff --git a/tests/src/index.json b/tests/src/index.json index 7549d300b..d965c7f75 100644 --- a/tests/src/index.json +++ b/tests/src/index.json @@ -676,6 +676,12 @@ "name" : "org.quartz-scheduler:quartz", "versions" : [ "2.3.2" ] } ] +}, { + "test-project-path" : "org.slf4j/slf4j-api/1.7.36", + "libraries" : [ { + "name" : "org.slf4j:slf4j-api", + "versions" : [ "1.7.36" ] + } ] }, { "test-project-path" : "org.testcontainers/testcontainers/1.17.6", "libraries" : [ { diff --git a/tests/src/org.slf4j/slf4j-api/1.7.36/.gitignore b/tests/src/org.slf4j/slf4j-api/1.7.36/.gitignore new file mode 100644 index 000000000..c98c7875b --- /dev/null +++ b/tests/src/org.slf4j/slf4j-api/1.7.36/.gitignore @@ -0,0 +1,4 @@ +gradlew.bat +gradlew +gradle/ +build/ diff --git a/tests/src/org.slf4j/slf4j-api/1.7.36/build.gradle b/tests/src/org.slf4j/slf4j-api/1.7.36/build.gradle new file mode 100644 index 000000000..414504e57 --- /dev/null +++ b/tests/src/org.slf4j/slf4j-api/1.7.36/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'org.graalvm.internal.tck' +} + +String libraryVersion = tck.testedLibraryVersion.get() + +dependencies { + implementation "org.slf4j:slf4j-api:$libraryVersion" + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' +} + +graalvmNative { + agent { + defaultMode = "conditional" + modes { + conditional { + userCodeFilterPath = "user-code-filter.json" + } + } + } +} diff --git a/tests/src/org.slf4j/slf4j-api/1.7.36/gradle.properties b/tests/src/org.slf4j/slf4j-api/1.7.36/gradle.properties new file mode 100644 index 000000000..aad133aed --- /dev/null +++ b/tests/src/org.slf4j/slf4j-api/1.7.36/gradle.properties @@ -0,0 +1,2 @@ +library.version = 1.7.36 +metadata.dir = org.slf4j/slf4j-api/1.7.36/ diff --git a/tests/src/org.slf4j/slf4j-api/1.7.36/settings.gradle b/tests/src/org.slf4j/slf4j-api/1.7.36/settings.gradle new file mode 100644 index 000000000..7188c7c94 --- /dev/null +++ b/tests/src/org.slf4j/slf4j-api/1.7.36/settings.gradle @@ -0,0 +1,13 @@ +pluginManagement { + def tckPath = Objects.requireNonNullElse( + System.getenv("GVM_TCK_TCKDIR"), + "../../../../tck-build-logic" + ) + includeBuild(tckPath) +} + +plugins { + id "org.graalvm.internal.tck-settings" version "1.0.0-SNAPSHOT" +} + +rootProject.name = 'org.slf4j.slf4j-api_tests' diff --git a/tests/src/org.slf4j/slf4j-api/1.7.36/src/test/java/org_slf4j/slf4j_api/Slf4j_apiTest.java b/tests/src/org.slf4j/slf4j-api/1.7.36/src/test/java/org_slf4j/slf4j_api/Slf4j_apiTest.java new file mode 100644 index 000000000..695bcb596 --- /dev/null +++ b/tests/src/org.slf4j/slf4j-api/1.7.36/src/test/java/org_slf4j/slf4j_api/Slf4j_apiTest.java @@ -0,0 +1,166 @@ +package org_slf4j.slf4j_api; + +import org.junit.jupiter.api.Test; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import org.slf4j.helpers.NOPLogger; +import org.slf4j.helpers.NOPLoggerFactory; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +public class Slf4j_apiTest { + + @Test + void iLoggerFactoryDefaultsToNOPWhenNoBinding() { + ILoggerFactory factory = LoggerFactory.getILoggerFactory(); + assertNotNull(factory, "ILoggerFactory should not be null"); + assertTrue(factory instanceof NOPLoggerFactory, + "Without a binding on the classpath, factory should be NOPLoggerFactory"); + + Logger logger = LoggerFactory.getLogger("test"); + assertNotNull(logger); + assertEquals("NOP", logger.getName()); + assertTrue(logger instanceof NOPLogger, "Logger should be NOPLogger under NOP factory"); + + // NOP should report all levels disabled + assertFalse(logger.isTraceEnabled()); + assertFalse(logger.isDebugEnabled()); + assertFalse(logger.isInfoEnabled()); + assertFalse(logger.isWarnEnabled()); + assertFalse(logger.isErrorEnabled()); + + // All logging calls should be safe no-ops + logger.trace("trace {}", 123); + logger.debug("debug {}", 123); + logger.info("info {}", 123); + logger.warn("warn {}", 123); + logger.error("error {}", 123); + logger.error("error with throwable", new RuntimeException("boom")); + } + + @Test + void mdcIsNoOpWithoutBinding_andIsThreadLocal() throws Exception { + // Make sure MDC starts clean + MDC.clear(); + + // With NOP adapter, put/get should be no-ops + MDC.put("foo", "bar"); + assertNull(MDC.get("foo"), "MDC.get should return null with NOP adapter"); + assertNull(MDC.getCopyOfContextMap(), "MDC.getCopyOfContextMap should be null with NOP adapter"); + + Map m = new HashMap<>(); + m.put("a", "b"); + MDC.setContextMap(m); + assertNull(MDC.get("a"), "MDC.setContextMap should be a no-op with NOP adapter"); + assertNull(MDC.getCopyOfContextMap()); + + // Verify MDC operations are thread-local (even though they are no-ops) + AtomicReference failure = new AtomicReference<>(); + Thread t = new Thread(() -> { + try { + MDC.put("k", "v"); + assertNull(MDC.get("k"), "MDC in a different thread should also be no-op"); + MDC.clear(); + assertNull(MDC.get("k")); + } catch (Throwable ex) { + failure.set(ex); + } + }); + t.start(); + t.join(); + if (failure.get() != null) { + throw new AssertionError("Failure in MDC thread", failure.get()); + } + + // Main thread should still be unaffected + assertNull(MDC.get("k")); + MDC.clear(); + } + + @Test + void markerFactoryFallsBackToBasic_andSupportsOperations() { + // Without a StaticMarkerBinder, MarkerFactory should fall back to BasicMarkerFactory + Object markerFactory = MarkerFactory.getIMarkerFactory(); + assertEquals("org.slf4j.helpers.BasicMarkerFactory", markerFactory.getClass().getName(), + "MarkerFactory should use BasicMarkerFactory when no binding is present"); + + Marker parent = MarkerFactory.getMarker("PARENT"); + Marker child = MarkerFactory.getMarker("CHILD"); + + assertEquals("PARENT", parent.getName()); + assertFalse(parent.hasReferences()); + assertFalse(parent.contains(child)); + assertFalse(parent.contains("CHILD")); + + parent.add(child); + assertTrue(parent.hasReferences()); + assertTrue(parent.contains(child)); + assertTrue(parent.contains("CHILD")); + + // Remove reference + assertTrue(parent.remove(child)); + assertFalse(parent.hasReferences()); + assertFalse(parent.contains(child)); + + // Detach from factory cache and ensure new instance is provided afterward + Marker d1 = MarkerFactory.getMarker("DETACH_ME"); + boolean detached = MarkerFactory.getIMarkerFactory().detachMarker("DETACH_ME"); + // Detach may return false if not present; still, next get should provide an instance which may differ. + Marker d2 = MarkerFactory.getMarker("DETACH_ME"); + // After a detach call, BasicMarkerFactory should return a fresh instance for the name + assertNotSame(d1, d2, "Detached marker should not be the same instance as the newly created marker"); + } + + @Test + void markerSerializationRoundTrip_preservesNameAndReferences() throws Exception { + Marker a = MarkerFactory.getMarker("A"); + Marker b = MarkerFactory.getMarker("B"); + a.add(b); + + Marker a2 = roundTrip(a); + assertNotNull(a2); + assertEquals("A", a2.getName()); + // BasicMarker implements contains(String) based on reference names + assertTrue(a2.contains("B"), "Deserialized marker should preserve references"); + } + + @Test + void loggingWithMarkersAndParametersDoesNotThrow() { + Logger logger = LoggerFactory.getLogger(Slf4j_apiTest.class); + Marker marker = MarkerFactory.getMarker("M"); + + logger.trace(marker, "trace {}", 1); + logger.debug(marker, "debug {} {}", "x", 2); + logger.info(marker, "info"); + logger.warn(marker, "warn with throwable", new IllegalStateException("warn")); + logger.error(marker, "error {} and {}", "A", "B"); + logger.error(marker, "error with throwable {}", 3, new RuntimeException("boom")); + + // Also call level-checks with marker (NOP returns false) + assertFalse(logger.isTraceEnabled(marker)); + assertFalse(logger.isDebugEnabled(marker)); + assertFalse(logger.isInfoEnabled(marker)); + assertFalse(logger.isWarnEnabled(marker)); + assertFalse(logger.isErrorEnabled(marker)); + } + + @SuppressWarnings("unchecked") + private static T roundTrip(T obj) throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(obj); + } + try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) { + return (T) ois.readObject(); + } + } +} diff --git a/tests/src/org.slf4j/slf4j-api/1.7.36/user-code-filter.json b/tests/src/org.slf4j/slf4j-api/1.7.36/user-code-filter.json new file mode 100644 index 000000000..932e03552 --- /dev/null +++ b/tests/src/org.slf4j/slf4j-api/1.7.36/user-code-filter.json @@ -0,0 +1,10 @@ +{ + "rules" : [ + { + "excludeClasses" : "**" + }, + { + "includeClasses" : "org.slf4j.**" + } + ] +} \ No newline at end of file