From f80bcda45414bb7318c4ae40091b6fdcea8ba029 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 4 Nov 2025 15:01:55 -0500 Subject: [PATCH 01/27] Adding listener support to NativeLoader --- .../CompositeLibraryLoadingListener.java | 118 ++++++++++++++++++ .../java/datadog/nativeloader/LibFile.java | 87 ++++++++++--- .../nativeloader/LibraryLoadingListener.java | 53 ++++++++ .../datadog/nativeloader/NativeLoader.java | 102 ++++++++++++--- .../NopLibraryLoadingListener.java | 19 +++ 5 files changed, 339 insertions(+), 40 deletions(-) create mode 100644 components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java create mode 100644 components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java create mode 100644 components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java new file mode 100644 index 00000000000..970fac3a218 --- /dev/null +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -0,0 +1,118 @@ +package datadog.nativeloader; + +import java.net.URL; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; + +final class CompositeLibraryLoadingListener extends SafeLibraryLoadingListener { + private final Collection listeners; + + CompositeLibraryLoadingListener(Collection listeners) { + this.listeners = listeners; + } + + @Override + public boolean isEmpty() { + return this.listeners.isEmpty(); + } + + @Override + public void onResolveDynamic( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + URL optionalUrl) { + for (LibraryLoadingListener listener : this.listeners) { + try { + listener.onResolveDynamic( + platformSpec, optionalComponent, libName, isPreloaded, optionalUrl); + } catch (Throwable ignored) { + } + } + } + + @Override + public void onResolveDynamicFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + for (LibraryLoadingListener listener : this.listeners) { + try { + listener.onResolveDynamicFailure(platformSpec, optionalComponent, libName, optionalCause); + } catch (Throwable ignored) { + } + } + } + + @Override + public void onLoad( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + Path optionalLibPath) { + for (LibraryLoadingListener listener : this.listeners) { + try { + listener.onLoad(platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath); + } catch (Throwable ignored) { + } + } + } + + @Override + public void onTempFileCreated( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + for (LibraryLoadingListener listener : this.listeners) { + try { + listener.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); + } catch (Throwable ignored) { + } + } + } + + @Override + public void onTempFileCreationFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + Path tempDir, + String libExt, + Path optionalTempFile, + Throwable optionalCause) { + for (LibraryLoadingListener listener : this.listeners) { + try { + listener.onTempFileCreationFailure( + platformSpec, + optionalComponent, + libName, + tempDir, + libExt, + optionalTempFile, + optionalCause); + } catch (Throwable ignored) { + } + } + } + + @Override + public void onTempFileCleanup( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempPath) { + for (LibraryLoadingListener listener : this.listeners) { + try { + listener.onTempFileCleanup(platformSpec, optionalComponent, libName, tempPath); + } catch (Throwable ignored) { + } + } + } + + @Override + public SafeLibraryLoadingListener join(LibraryLoadingListener listener) { + ArrayList listeners = new ArrayList<>(this.listeners.size() + 1); + listeners.addAll(this.listeners); + listeners.add(listener); + return new CompositeLibraryLoadingListener(listeners); + } +} diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java b/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java index fb2e27d61d4..866ec89601d 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java @@ -7,75 +7,122 @@ * Represents a resolved library * * */ public final class LibFile implements AutoCloseable { static final boolean NO_CLEAN_UP = false; static final boolean CLEAN_UP = true; - static final LibFile preloaded(String libName) { - return new LibFile(libName, null, NO_CLEAN_UP); + static final LibFile preloaded( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + SafeLibraryLoadingListener listeners) { + return new LibFile(platformSpec, optionalComponent, libName, null, NO_CLEAN_UP, listeners); } - static final LibFile fromFile(String libName, File file) { - return new LibFile(libName, file, NO_CLEAN_UP); + static final LibFile fromFile( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + File optionalFile, + SafeLibraryLoadingListener listeners) { + return new LibFile( + platformSpec, optionalComponent, libName, optionalFile, NO_CLEAN_UP, listeners); } - static final LibFile fromTempFile(String libName, File file) { - return new LibFile(libName, file, CLEAN_UP); + static final LibFile fromTempFile( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + File optionalFile, + SafeLibraryLoadingListener listeners) { + return new LibFile(platformSpec, optionalComponent, libName, optionalFile, CLEAN_UP, listeners); } + final PlatformSpec platformSpec; + final String optionalComponent; final String libName; - final File file; + final File optionalFile; final boolean needsCleanup; - LibFile(String libName, File file, boolean needsCleanup) { + final SafeLibraryLoadingListener listeners; + + LibFile( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + File optionalFile, + boolean needsCleanup, + SafeLibraryLoadingListener listeners) { + this.platformSpec = platformSpec; + this.optionalComponent = optionalComponent; this.libName = libName; - this.file = file; + this.optionalFile = optionalFile; this.needsCleanup = needsCleanup; + + this.listeners = listeners; } /** Indicates if this library was "preloaded" */ public boolean isPreloaded() { - return (this.file == null); + return (this.optionalFile == null); } /** Loads the underlying library into the JVM */ public void load() throws LibraryLoadException { - if (this.isPreloaded()) return; + boolean isPreloaded = this.isPreloaded(); + if (isPreloaded) { + this.listeners.onLoad( + this.platformSpec, this.optionalComponent, this.libName, isPreloaded, null); + return; + } try { Runtime.getRuntime().load(this.getAbsolutePath()); + + this.listeners.onLoad( + this.platformSpec, + this.optionalComponent, + this.libName, + isPreloaded, + this.optionalFile.toPath()); } catch (Throwable t) { - throw new LibraryLoadException(this.libName, t); + LibraryLoadException ex = new LibraryLoadException(this.libName, t); + this.listeners.onLoadFailure(this.platformSpec, this.optionalComponent, this.libName, ex); + throw ex; } } /** Provides a File to the library -- returns null for pre-loaded libraries */ public final File toFile() { - return this.file; + return this.optionalFile; } /** Provides a Path to the library -- return null for pre-loaded libraries */ public final Path toPath() { - return this.file == null ? null : this.file.toPath(); + return this.optionalFile == null ? null : this.optionalFile.toPath(); } /** Provides the an absolute path to the library -- returns null for pre-loaded libraries */ public final String getAbsolutePath() { - return this.file == null ? null : this.file.getAbsolutePath(); + return this.optionalFile == null ? null : this.optionalFile.getAbsolutePath(); } - /** Schedules clean-up of underlying file -- if the file is a temp file */ + /** Schedules clean-up of underlying optionalFile -- if the file is a temp file */ @Override public void close() { if (this.needsCleanup) { - NativeLoader.delete(this.file); + boolean done = NativeLoader.delete(this.optionalFile); + if (done) { + this.listeners.onTempFileCleanup( + this.platformSpec, this.optionalComponent, this.libName, this.optionalFile.toPath()); + } } } } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java new file mode 100644 index 00000000000..527d4177c1e --- /dev/null +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -0,0 +1,53 @@ +package datadog.nativeloader; + +import java.net.URL; +import java.nio.file.Path; + +public interface LibraryLoadingListener { + default void onResolveDynamic( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + URL optionalUrl) {} + + default void onResolveDynamicFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) {} + + default void onLoad( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + Path optionalLibPath) {} + + default void onLoadFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) {} + + default void onTempFileCreated( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) {} + + default void onTempFileCreationFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + Path tempDir, + String libExt, + Path optionalTempFile, + Throwable optionalCause) {} + + default void onTempFileCleanup( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) {} +} + +abstract class SafeLibraryLoadingListener implements LibraryLoadingListener { + public abstract SafeLibraryLoadingListener join(LibraryLoadingListener listener); + + public abstract boolean isEmpty(); +} diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 17af0b6f7ab..f97522259a1 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -11,6 +11,9 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Set; /** @@ -26,6 +29,7 @@ public static final class Builder { private String[] preloadedLibNames; private LibraryResolver libResolver; private PathLocator pathLocator; + private List listeners = new ArrayList<>(); Builder() {} @@ -138,6 +142,16 @@ public Builder tempDir(String tmpPath) { return this.tempDir(Paths.get(tmpPath)); } + public Builder addListener(LibraryLoadingListener listener) { + this.listeners.add(listener); + return this; + } + + public Builder addListeners(LibraryLoadingListener... listeners) { + this.listeners.addAll(Arrays.asList(listeners)); + return this; + } + /** Constructs and returns the {@link NativeLoader} */ public NativeLoader build() { return new NativeLoader(this); @@ -151,6 +165,12 @@ PathLocator pathLocator() { return (this.pathLocator == null) ? PathLocators.defaultPathLocator() : this.pathLocator; } + SafeLibraryLoadingListener listeners() { + return this.listeners.isEmpty() + ? NopLibraryLoadingListener.INSTANCE + : new CompositeLibraryLoadingListener(this.listeners); + } + LibraryResolver libResolver() { LibraryResolver baseResolver = (this.libResolver == null) ? LibraryResolvers.defaultLibraryResolver() : this.libResolver; @@ -172,12 +192,14 @@ public static final Builder builder() { private final PlatformSpec defaultPlatformSpec; private final LibraryResolver libResolver; private final PathLocator pathResolver; + private final SafeLibraryLoadingListener listeners; private final Path tempDir; private NativeLoader(Builder builder) { this.defaultPlatformSpec = builder.platformSpec(); this.libResolver = builder.libResolver(); this.pathResolver = builder.pathLocator(); + this.listeners = builder.listeners(); this.tempDir = builder.tempDir(); } @@ -193,24 +215,32 @@ public boolean isPreloaded(PlatformSpec platformSpec, String libName) { /** Loads a library */ public void load(String libName) throws LibraryLoadException { - this.load(null, libName); + this.loadImpl(null, libName, null); + } + + public void load(String libName, LibraryLoadingListener listener) throws LibraryLoadException { + this.loadImpl(null, libName, listener); } /** Loads a library associated with an associated component */ - public void load(String component, String libName) throws LibraryLoadException { - try (LibFile libFile = this.resolveDynamic(component, libName)) { + public void load(String component, String libName) throws LibraryLoadException {} + + private void loadImpl(String component, String libName, LibraryLoadingListener listener) + throws LibraryLoadException { + try (LibFile libFile = + this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName, listener)) { libFile.load(); } } /** Resolves a library to a LibFile - creating a temporary file if necessary */ public LibFile resolveDynamic(String libName) throws LibraryLoadException { - return this.resolveDynamic((String) null, libName); + return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName, null); } /** Resolves a library with an associated component */ public LibFile resolveDynamic(String component, String libName) throws LibraryLoadException { - return this.resolveDynamic(component, this.defaultPlatformSpec, libName); + return this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName, null); } /** @@ -219,62 +249,93 @@ public LibFile resolveDynamic(String component, String libName) throws LibraryLo */ public LibFile resolveDynamic(PlatformSpec platformSpec, String libName) throws LibraryLoadException { - return this.resolveDynamic(null, platformSpec, libName); + return this.resolveDynamicImpl(platformSpec, null, libName, null); } /** * Resolves a library with an associated component with a different {@link PlatformSpec} than the * default */ - public LibFile resolveDynamic(String component, PlatformSpec platformSpec, String libName) + public LibFile resolveDynamic(PlatformSpec platformSpec, String component, String libName) + throws LibraryLoadException { + return this.resolveDynamicImpl(platformSpec, component, libName, null); + } + + private LibFile resolveDynamicImpl( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadingListener listener) throws LibraryLoadException { + SafeLibraryLoadingListener listeners = + (listener == null) ? this.listeners : this.listeners.join(listener); + if (platformSpec.isUnknownOs() || platformSpec.isUnknownArch()) { - throw new LibraryLoadException(libName, "Unsupported platform"); + LibraryLoadException ex = new LibraryLoadException(libName, "Unsupported platform"); + listeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); + throw ex; } if (this.isPreloaded(platformSpec, libName)) { - return LibFile.preloaded(libName); + return LibFile.preloaded(platformSpec, optionalComponent, libName, listeners); } URL url; try { - url = this.libResolver.resolve(this.pathResolver, component, platformSpec, libName); + url = this.libResolver.resolve(this.pathResolver, optionalComponent, platformSpec, libName); } catch (LibraryLoadException e) { // don't wrap if it is already a LibraryLoadException + listeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, e); throw e; } catch (Throwable t) { - throw new LibraryLoadException(libName, t); + LibraryLoadException ex = new LibraryLoadException(libName, t); + listeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); + throw ex; } if (url == null) { - throw new LibraryLoadException(libName); + LibraryLoadException ex = new LibraryLoadException(libName); + listeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); + throw ex; } - return toLibFile(platformSpec, libName, url); + + listeners.onResolveDynamic(platformSpec, optionalComponent, libName, url); + return this.toLibFile(platformSpec, optionalComponent, libName, url, listeners); } - private LibFile toLibFile(PlatformSpec platformSpec, String libName, URL url) + private LibFile toLibFile( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + URL url, + SafeLibraryLoadingListener listeners) throws LibraryLoadException { if (url.getProtocol().equals("file")) { - return LibFile.fromFile(libName, new File(url.getPath())); + return LibFile.fromFile( + platformSpec, optionalComponent, libName, new File(url.getPath()), listeners); } else { String libExt = PathUtils.dynamicLibExtension(platformSpec); + Path tempFile = null; try { - Path tempFile = TempFileHelper.createTempFile(this.tempDir, libName, libExt); + tempFile = TempFileHelper.createTempFile(this.tempDir, libName, libExt); try (InputStream in = url.openStream()) { Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING); } - return LibFile.fromTempFile(libName, tempFile.toFile()); + return LibFile.fromTempFile( + platformSpec, optionalComponent, libName, tempFile.toFile(), listeners); } catch (Throwable t) { + listeners.onTempFileCreationFailure( + platformSpec, optionalComponent, libName, this.tempDir, libExt, tempFile, t); throw new LibraryLoadException(libName, t); } } } - static void delete(File tempFile) { - TempFileHelper.delete(tempFile); + static boolean delete(File tempFile) { + return TempFileHelper.delete(tempFile); } static final class TempFileHelper { @@ -296,9 +357,10 @@ static Path createTempFile(Path tempDir, String libname, String libExt) } } - static void delete(File tempFile) { + static boolean delete(File tempFile) { boolean deleted = tempFile.delete(); if (!deleted) tempFile.deleteOnExit(); + return deleted; } } } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java new file mode 100644 index 00000000000..782dc495c58 --- /dev/null +++ b/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java @@ -0,0 +1,19 @@ +package datadog.nativeloader; + +import java.util.Collections; + +final class NopLibraryLoadingListener extends SafeLibraryLoadingListener { + static final NopLibraryLoadingListener INSTANCE = new NopLibraryLoadingListener(); + + private NopLibraryLoadingListener() {} + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public SafeLibraryLoadingListener join(LibraryLoadingListener listener) { + return new CompositeLibraryLoadingListener(Collections.singletonList(listener)); + } +} From 54af840f32d2a86373c7afa41f67c88089a9597e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 09:49:21 -0500 Subject: [PATCH 02/27] Adding listener tests - adding test to CompositeLibraryLoadingListener --- .../CompositeLibraryLoadingListener.java | 5 + .../datadog/nativeloader/NativeLoader.java | 6 +- .../CompositeLibraryLoadingListenerTest.java | 126 +++++++ .../TestLibraryLoadingListener.java | 321 ++++++++++++++++++ .../ThrowingLibraryLoadingListener.java | 72 ++++ 5 files changed, 528 insertions(+), 2 deletions(-) create mode 100644 components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java create mode 100644 components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java create mode 100644 components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index 970fac3a218..bde5a9eb302 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -3,11 +3,16 @@ import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; final class CompositeLibraryLoadingListener extends SafeLibraryLoadingListener { private final Collection listeners; + CompositeLibraryLoadingListener(LibraryLoadingListener... listeners) { + this(Arrays.asList(listeners)); + } + CompositeLibraryLoadingListener(Collection listeners) { this.listeners = listeners; } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index f97522259a1..537637d53f6 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -276,7 +276,9 @@ private LibFile resolveDynamicImpl( throw ex; } - if (this.isPreloaded(platformSpec, libName)) { + boolean isPreloaded = this.isPreloaded(platformSpec, libName); + if (isPreloaded) { + listeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, null); return LibFile.preloaded(platformSpec, optionalComponent, libName, listeners); } @@ -299,7 +301,7 @@ private LibFile resolveDynamicImpl( throw ex; } - listeners.onResolveDynamic(platformSpec, optionalComponent, libName, url); + listeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, url); return this.toLibFile(platformSpec, optionalComponent, libName, url, listeners); } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java new file mode 100644 index 00000000000..d5f24f618f6 --- /dev/null +++ b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java @@ -0,0 +1,126 @@ +package datadog.nativeloader; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +public final class CompositeLibraryLoadingListenerTest { + @Test + public void onResolveDynamic() throws MalformedURLException { + TestLibraryLoadingListener listener1 = + new TestLibraryLoadingListener().expectResolveDynamic("foo"); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + listeners(listener1, listener2) + .onResolveDynamic( + PlatformSpec.defaultPlatformSpec(), null, "foo", false, new URL("http://localhost")); + + listener1.assertDone(); + listener2.assertDone(); + } + + @Test + public void onResolveDynamicFailure() { + TestLibraryLoadingListener listener1 = + new TestLibraryLoadingListener().expectResolveDynamicFailure("foo"); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + listeners(listener1, listener2) + .onResolveDynamicFailure( + PlatformSpec.defaultPlatformSpec(), null, "foo", new LibraryLoadException("foo")); + + listener1.assertDone(); + listener2.assertDone(); + } + + @Test + public void onLoad() { + TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener().expectLoad("foo"); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + listeners(listener1, listener2) + .onResolveDynamicFailure( + PlatformSpec.defaultPlatformSpec(), null, "foo", new LibraryLoadException("foo")); + + listener1.assertDone(); + listener2.assertDone(); + } + + @Test + public void onTempFileCreated() { + TestLibraryLoadingListener listener1 = + new TestLibraryLoadingListener().expectTempFileCreated("foo"); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + listeners(listener1, listener2) + .onTempFileCreated( + PlatformSpec.defaultPlatformSpec(), null, "foo", Paths.get("/tmp/foo.dll")); + + listener1.assertDone(); + listener2.assertDone(); + } + + @Test + public void onTempFileCreationFailure() { + TestLibraryLoadingListener listener1 = + new TestLibraryLoadingListener().expectTempFileCreationFailure("foo"); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + listeners(listener1, listener2) + .onTempFileCreationFailure( + PlatformSpec.defaultPlatformSpec(), + null, + "foo", + Paths.get("/tmp"), + "dylib", + null, + new IOException("perm")); + + listener1.assertDone(); + listener2.assertDone(); + } + + @Test + public void onTempFileCleanup() { + TestLibraryLoadingListener listener1 = + new TestLibraryLoadingListener().expectTempFileCleanup("foo"); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + listeners(listener1, listener2) + .onTempFileCleanup( + PlatformSpec.defaultPlatformSpec(), null, "foo", Paths.get("/tmp/foo.dll")); + + listener1.assertDone(); + listener2.assertDone(); + } + + /* + * Constructs a composite listener that includes the provided listeners + * To test robustness... + * - adds in additional failing listeners + * - shuffles the order of the listeners + */ + static CompositeLibraryLoadingListener listeners(LibraryLoadingListener... listeners) { + List shuffledListeners = new ArrayList<>(listeners.length + 1); + shuffledListeners.addAll(Arrays.asList(listeners)); + + for (int i = 0; i < listeners.length; ++i) { + shuffledListeners.add(new ThrowingLibraryLoadingListener()); + } + + Collections.shuffle(shuffledListeners); + return new CompositeLibraryLoadingListener(listeners); + } +} diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java new file mode 100644 index 00000000000..6742428475e --- /dev/null +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -0,0 +1,321 @@ +package datadog.nativeloader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.URL; +import java.nio.file.Path; +import java.util.LinkedList; + +public final class TestLibraryLoadingListener implements LibraryLoadingListener { + private final LinkedList checks; + + TestLibraryLoadingListener() { + this.checks = new LinkedList<>(); + } + + TestLibraryLoadingListener(TestLibraryLoadingListener that) { + this.checks = new LinkedList<>(that.checks); + } + + public TestLibraryLoadingListener expectResolveDynamic(String expectedLibName) { + return this.addCheck( + new Check("onResolveDynamic %s", expectedLibName) { + @Override + public void onResolveDynamic( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + URL optionalUrl) { + assertNull(optionalComponent); + assertEquals(libName, expectedLibName); + } + }); + } + + public TestLibraryLoadingListener expectResolveDynamicFailure(String expectedLibName) { + return this.addCheck( + new Check("onResolveDynamicFailure %s", expectedLibName) { + @Override + public void onResolveDynamicFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + assertNull(optionalComponent); + assertEquals(libName, expectedLibName); + } + }); + } + + public TestLibraryLoadingListener expectLoad(String expectedLibName) { + return this.addCheck( + new Check("onLoad %s", expectedLibName) { + @Override + public void onLoad( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + Path optionalLibPath) { + assertNull(optionalComponent); + assertEquals(libName, expectedLibName); + } + }); + } + + public TestLibraryLoadingListener expectLoad(String expectedComponent, String expectedLibName) { + return this.addCheck( + new Check("onLoad %s/%s", expectedComponent, expectedLibName) { + @Override + public void onLoad( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + Path optionalLibPath) { + assertEquals(optionalComponent, expectedComponent); + assertEquals(libName, expectedLibName); + } + }); + } + + public TestLibraryLoadingListener expectTempFileCreated(String expectedLibName) { + return this.addCheck( + new Check("onTempFileCreated %s", expectedLibName) { + @Override + public void onTempFileCreated( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + assertNull(optionalComponent); + assertEquals(expectedLibName, libName); + } + }); + } + + public TestLibraryLoadingListener expectTempFileCreationFailure(String expectedLibName) { + return this.addCheck( + new Check("onTempFileCreationFailure %s", expectedLibName) { + @Override + public void onTempFileCreationFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + Path tempDir, + String libExt, + Path optionalTempFile, + Throwable optionalCause) { + assertNull(optionalComponent); + assertEquals(expectedLibName, libName); + } + }); + } + + public TestLibraryLoadingListener expectTempFileCleanup(String expectedLibName) { + return this.addCheck( + new Check("onTempFileCreationCleanup %s", expectedLibName) { + @Override + public void onTempFileCleanup( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + assertNull(optionalComponent); + assertEquals(expectedLibName, libName); + } + }); + } + + public TestLibraryLoadingListener copy() { + return new TestLibraryLoadingListener(this); + } + + public void assertDone() { + assertTrue(this.checks.isEmpty()); + } + + @Override + public void onResolveDynamic( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + URL optionalUrl) { + this.nextCheck() + .onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, optionalUrl); + } + + @Override + public void onResolveDynamicFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + this.nextCheck() + .onResolveDynamicFailure(platformSpec, optionalComponent, libName, optionalCause); + } + + @Override + public void onLoad( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + Path optionalLibPath) { + this.nextCheck().onLoad(platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath); + } + + @Override + public void onLoadFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + this.nextCheck().onLoadFailure(platformSpec, optionalComponent, libName, optionalCause); + } + + @Override + public void onTempFileCreated( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + this.nextCheck().onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); + } + + @Override + public void onTempFileCreationFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + Path tempDir, + String libExt, + Path optionalTempFile, + Throwable optionalCause) { + this.nextCheck() + .onTempFileCreationFailure( + platformSpec, + optionalComponent, + libName, + tempDir, + libExt, + optionalTempFile, + optionalCause); + } + + @Override + public void onTempFileCleanup( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + this.nextCheck().onTempFileCleanup(platformSpec, optionalComponent, libName, tempFile); + } + + TestLibraryLoadingListener addCheck(Check check) { + this.checks.addLast(check); + return this; + } + + Check nextCheck() { + return this.checks.isEmpty() ? Check.NOTHING : this.checks.removeFirst(); + } + + abstract static class Check implements LibraryLoadingListener { + static final Check NOTHING = new Check("nothing") {}; + + private final String name; + + Check(String nameFormat, Object... nameArgs) { + this(String.format(nameFormat, nameArgs)); + } + + Check(String name) { + this.name = name; + } + + @Override + public void onLoad( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + Path optionalLibPath) { + this.fail("onLoad", platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath); + } + + @Override + public void onLoadFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + this.fail("onLoadFailure", platformSpec, optionalComponent, libName, optionalCause); + } + + @Override + public void onResolveDynamic( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + URL optionalUrl) { + this.fail( + "onResolveDynamic", platformSpec, optionalComponent, libName, isPreloaded, optionalUrl); + } + + @Override + public void onResolveDynamicFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + this.fail("onResolveDynamicFailure", platformSpec, optionalComponent, libName, optionalCause); + } + + @Override + public void onTempFileCreated( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + this.fail("onTmepFileCreated", platformSpec, optionalComponent, libName, tempFile); + } + + @Override + public void onTempFileCreationFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + Path tempDir, + String libExt, + Path optionalTempFile, + Throwable optionalCause) { + this.fail( + "onTempFileCreationFailure", + platformSpec, + optionalComponent, + libName, + tempDir, + libExt, + optionalTempFile, + optionalCause); + } + + @Override + public void onTempFileCleanup( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + this.fail("onTempFileCleanup", platformSpec, optionalComponent, libName, tempFile); + } + + void fail(String methodName, Object... args) { + fail("unxpected call: " + callToString(methodName, args) + " - expected: " + this.name); + } + + static final String callToString(String methodName, Object... args) { + StringBuilder builder = new StringBuilder(); + builder.append(methodName); + builder.append('('); + for (int i = 0; i < args.length; ++i) { + if (i != 0) builder.append(", "); + builder.append(args[i].toString()); + } + builder.append(')'); + return builder.toString(); + } + + @Override + public String toString() { + return this.name; + } + } +} diff --git a/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java new file mode 100644 index 00000000000..c9f73b0580b --- /dev/null +++ b/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java @@ -0,0 +1,72 @@ +package datadog.nativeloader; + +import java.net.URL; +import java.nio.file.Path; + +public class ThrowingLibraryLoadingListener implements LibraryLoadingListener { + @Override + public final void onLoad( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + Path optionalLibPath) { + this.throwException("load"); + } + + @Override + public final void onLoadFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + this.throwException("loadFailure"); + } + + @Override + public final void onResolveDynamic( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + URL optionalUrl) { + this.throwException("resolveDynamic"); + } + + @Override + public final void onResolveDynamicFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + this.throwException("resolveDynamicFailure"); + } + + @Override + public final void onTempFileCreated( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + this.throwException("tempFileCreated"); + } + + @Override + public final void onTempFileCreationFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + Path tempDir, + String libExt, + Path optionalTempFile, + Throwable optionalCause) { + this.throwException("tempFileCreationFailure"); + } + + @Override + public final void onTempFileCleanup( + PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { + this.throwException("tempFileCleanup"); + } + + void throwException(String event) { + throw new RuntimeException(event); + } +} From 035ba29b787beb27880d220f387f3bfa4b96494f Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 10:07:35 -0500 Subject: [PATCH 03/27] isEmpty -> isNop to better convey purpose --- .../datadog/nativeloader/CompositeLibraryLoadingListener.java | 2 +- .../main/java/datadog/nativeloader/LibraryLoadingListener.java | 2 +- .../java/datadog/nativeloader/NopLibraryLoadingListener.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index bde5a9eb302..0e5c0ae8b3e 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -18,7 +18,7 @@ final class CompositeLibraryLoadingListener extends SafeLibraryLoadingListener { } @Override - public boolean isEmpty() { + public boolean isNop() { return this.listeners.isEmpty(); } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index 527d4177c1e..b0f43e0cafe 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -49,5 +49,5 @@ default void onTempFileCleanup( abstract class SafeLibraryLoadingListener implements LibraryLoadingListener { public abstract SafeLibraryLoadingListener join(LibraryLoadingListener listener); - public abstract boolean isEmpty(); + public abstract boolean isNop(); } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java index 782dc495c58..b55d3bf3dbc 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java @@ -8,7 +8,7 @@ final class NopLibraryLoadingListener extends SafeLibraryLoadingListener { private NopLibraryLoadingListener() {} @Override - public boolean isEmpty() { + public boolean isNop() { return true; } From fa9428e94716af76d27a281ae6db1485a544ac1e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 10:42:40 -0500 Subject: [PATCH 04/27] Adding listener tests - adding test of NativeLoader.Builder listener related methods --- .../nativeloader/NativeLoaderBuilderTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderBuilderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderBuilderTest.java index db04ea859e1..b638059a966 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderBuilderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderBuilderTest.java @@ -25,6 +25,60 @@ public void customPlatformSpec() { NativeLoader.Builder builder = NativeLoader.builder().platformSpec(platformSpec); assertSame(platformSpec, builder.platformSpec()); } + + @Test + public void defaultListeners() { + NativeLoader.Builder builder = NativeLoader.builder(); + + assertTrue(builder.listeners().isNop()); + } + + @Test + public void addListener() { + TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener(). + expectLoad("foo"); + TestLibraryLoadingListener listener2 = listener1.copy(); + + NativeLoader.Builder builder = NativeLoader.builder(). + addListener(listener1). + addListener(listener2); + + SafeLibraryLoadingListener listener = builder.listeners(); + assertFalse(listener.isNop()); + + listener.onLoad( + builder.platformSpec(), + null, + "foo", + false, + Paths.get("/tmp/foo.dylib")); + + listener1.assertDone(); + listener2.assertDone(); + } + + @Test + public void addListeners() { + TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener(). + expectLoad("foo"); + TestLibraryLoadingListener listener2 = listener1.copy(); + + NativeLoader.Builder builder = NativeLoader.builder(). + addListeners(listener1, listener2); + + SafeLibraryLoadingListener listener = builder.listeners(); + assertFalse(listener.isNop()); + + listener.onLoad( + builder.platformSpec(), + null, + "foo", + false, + Paths.get("/tmp/foo.dylib")); + + listener1.assertDone(); + listener2.assertDone(); + } @Test public void defaultLibraryResolver() { From f7edec97707aed3b2bbb0ff76eb8ac4c369115bc Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 12:33:57 -0500 Subject: [PATCH 05/27] Adding listener tests - adding end-to-end test for preloading listener support --- .../CompositeLibraryLoadingListener.java | 14 ++-- .../nativeloader/LibraryLoadingListener.java | 2 +- .../datadog/nativeloader/NativeLoader.java | 59 +++++++++++------ .../NopLibraryLoadingListener.java | 6 +- .../CompositeLibraryLoadingListenerTest.java | 34 ++++++++++ .../nativeloader/NativeLoaderTest.java | 66 +++++++++++++++++-- .../TestLibraryLoadingListener.java | 38 ++++++++++- 7 files changed, 182 insertions(+), 37 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index 0e5c0ae8b3e..40f117d975e 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -21,6 +21,10 @@ final class CompositeLibraryLoadingListener extends SafeLibraryLoadingListener { public boolean isNop() { return this.listeners.isEmpty(); } + + int size() { + return this.listeners.size(); + } @Override public void onResolveDynamic( @@ -114,10 +118,10 @@ public void onTempFileCleanup( } @Override - public SafeLibraryLoadingListener join(LibraryLoadingListener listener) { - ArrayList listeners = new ArrayList<>(this.listeners.size() + 1); - listeners.addAll(this.listeners); - listeners.add(listener); - return new CompositeLibraryLoadingListener(listeners); + public CompositeLibraryLoadingListener join(LibraryLoadingListener... listeners) { + ArrayList combinedListeners = new ArrayList<>(this.listeners.size() + listeners.length); + combinedListeners.addAll(this.listeners); + combinedListeners.addAll(Arrays.asList(listeners)); + return new CompositeLibraryLoadingListener(combinedListeners); } } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index b0f43e0cafe..7a3e86bd7cb 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -47,7 +47,7 @@ default void onTempFileCleanup( } abstract class SafeLibraryLoadingListener implements LibraryLoadingListener { - public abstract SafeLibraryLoadingListener join(LibraryLoadingListener listener); + public abstract SafeLibraryLoadingListener join(LibraryLoadingListener... listeners); public abstract boolean isNop(); } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 537637d53f6..9600474237a 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -188,6 +188,8 @@ Path tempDir() { public static final Builder builder() { return new Builder(); } + + private static final LibraryLoadingListener[] EMPTY_LISTENERS = {}; private final PlatformSpec defaultPlatformSpec; private final LibraryResolver libResolver; @@ -215,32 +217,38 @@ public boolean isPreloaded(PlatformSpec platformSpec, String libName) { /** Loads a library */ public void load(String libName) throws LibraryLoadException { - this.loadImpl(null, libName, null); + this.loadImpl(null, libName, EMPTY_LISTENERS); } - public void load(String libName, LibraryLoadingListener listener) throws LibraryLoadException { - this.loadImpl(null, libName, listener); + public void load(String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { + this.loadImpl(null, libName, scopedListeners); } /** Loads a library associated with an associated component */ public void load(String component, String libName) throws LibraryLoadException {} - private void loadImpl(String component, String libName, LibraryLoadingListener listener) + private void loadImpl(String component, String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { + + // scopedListeners are attached to the LibFile by resolveDynamicImpl try (LibFile libFile = - this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName, listener)) { + this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName, scopedListeners)) { libFile.load(); } } /** Resolves a library to a LibFile - creating a temporary file if necessary */ public LibFile resolveDynamic(String libName) throws LibraryLoadException { - return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName, null); + return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName); + } + + public LibFile resolveDynamic(String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { + return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName, scopedListeners); } /** Resolves a library with an associated component */ public LibFile resolveDynamic(String component, String libName) throws LibraryLoadException { - return this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName, null); + return this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName); } /** @@ -249,7 +257,7 @@ public LibFile resolveDynamic(String component, String libName) throws LibraryLo */ public LibFile resolveDynamic(PlatformSpec platformSpec, String libName) throws LibraryLoadException { - return this.resolveDynamicImpl(platformSpec, null, libName, null); + return this.resolveDynamicImpl(platformSpec, null, libName); } /** @@ -258,28 +266,39 @@ public LibFile resolveDynamic(PlatformSpec platformSpec, String libName) */ public LibFile resolveDynamic(PlatformSpec platformSpec, String component, String libName) throws LibraryLoadException { - return this.resolveDynamicImpl(platformSpec, component, libName, null); + return this.resolveDynamicImpl(platformSpec, component, libName); + } + + private LibFile resolveDynamicImpl( + PlatformSpec platformSpec, + String optionalComponent, + String libName) + throws LibraryLoadException + { + return this.resolveDynamicImpl(platformSpec, optionalComponent, libName, EMPTY_LISTENERS); } private LibFile resolveDynamicImpl( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadingListener listener) + LibraryLoadingListener... scopedListeners) throws LibraryLoadException { - SafeLibraryLoadingListener listeners = - (listener == null) ? this.listeners : this.listeners.join(listener); + SafeLibraryLoadingListener allListeners = + (scopedListeners == null || scopedListeners == EMPTY_LISTENERS || scopedListeners.length == 0) ? + this.listeners : + this.listeners.join(scopedListeners); if (platformSpec.isUnknownOs() || platformSpec.isUnknownArch()) { LibraryLoadException ex = new LibraryLoadException(libName, "Unsupported platform"); - listeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); + allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); throw ex; } boolean isPreloaded = this.isPreloaded(platformSpec, libName); if (isPreloaded) { - listeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, null); - return LibFile.preloaded(platformSpec, optionalComponent, libName, listeners); + allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, null); + return LibFile.preloaded(platformSpec, optionalComponent, libName, allListeners); } URL url; @@ -287,22 +306,22 @@ private LibFile resolveDynamicImpl( url = this.libResolver.resolve(this.pathResolver, optionalComponent, platformSpec, libName); } catch (LibraryLoadException e) { // don't wrap if it is already a LibraryLoadException - listeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, e); + allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, e); throw e; } catch (Throwable t) { LibraryLoadException ex = new LibraryLoadException(libName, t); - listeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); + allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); throw ex; } if (url == null) { LibraryLoadException ex = new LibraryLoadException(libName); - listeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); + allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); throw ex; } - listeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, url); - return this.toLibFile(platformSpec, optionalComponent, libName, url, listeners); + allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, url); + return this.toLibFile(platformSpec, optionalComponent, libName, url, allListeners); } private LibFile toLibFile( diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java index b55d3bf3dbc..afec8dc1b1f 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NopLibraryLoadingListener.java @@ -1,6 +1,6 @@ package datadog.nativeloader; -import java.util.Collections; +import java.util.Arrays; final class NopLibraryLoadingListener extends SafeLibraryLoadingListener { static final NopLibraryLoadingListener INSTANCE = new NopLibraryLoadingListener(); @@ -13,7 +13,7 @@ public boolean isNop() { } @Override - public SafeLibraryLoadingListener join(LibraryLoadingListener listener) { - return new CompositeLibraryLoadingListener(Collections.singletonList(listener)); + public SafeLibraryLoadingListener join(LibraryLoadingListener... listeners) { + return new CompositeLibraryLoadingListener(Arrays.asList(listeners)); } } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java index d5f24f618f6..09d962b63d1 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java @@ -1,5 +1,7 @@ package datadog.nativeloader; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -105,6 +107,38 @@ public void onTempFileCleanup() { listener1.assertDone(); listener2.assertDone(); } + + @Test + public void join() { + TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener(). + expectLoad("foo"); + + CompositeLibraryLoadingListener composite = new CompositeLibraryLoadingListener(listener1); + assertEquals(1, composite.size()); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + CompositeLibraryLoadingListener composite2 = composite.join(listener2); + assertEquals(2, composite2.size()); + + TestLibraryLoadingListener listener3 = listener1.copy(); + TestLibraryLoadingListener listener4 = listener1.copy(); + + CompositeLibraryLoadingListener finalComposite = composite2.join(listener3, listener4); + assertEquals(4, finalComposite.size()); + + finalComposite.onLoad( + PlatformSpec.defaultPlatformSpec(), + null, + "foo", + false, + Paths.get("/tmp/foo.dll")); + + listener1.assertDone(); + listener2.assertDone(); + listener3.assertDone(); + listener4.assertDone(); + } /* * Constructs a composite listener that includes the provided listeners diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index a122e88bea4..e879d7b5fe3 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -30,14 +30,14 @@ public class NativeLoaderTest { @Test public void preloaded() throws LibraryLoadException { - NativeLoader loader = NativeLoader.builder().preloaded("dne1", "dne2").build(); + NativeLoader loader = NativeLoader.builder().preloaded("preloaded1", "preloaded2").build(); - assertTrue(loader.isPreloaded("dne1")); - assertTrue(loader.isPreloaded("dne2")); + assertTrue(loader.isPreloaded("preloaded1")); + assertTrue(loader.isPreloaded("preloaded2")); - assertFalse(loader.isPreloaded("dne3")); + assertFalse(loader.isPreloaded("dne")); - try (LibFile lib = loader.resolveDynamic("dne1")) { + try (LibFile lib = loader.resolveDynamic("preloaded1")) { assertPreloaded(lib); // already considered loaded -- so this is a nop @@ -45,10 +45,62 @@ public void preloaded() throws LibraryLoadException { } // already considered loaded -- so this is a nop - loader.load("dne2"); + loader.load("preloaded2"); // not already loaded - so passes through to underlying resolver - assertThrows(LibraryLoadException.class, () -> loader.load("dne3")); + assertThrows(LibraryLoadException.class, () -> loader.load("dne")); + } + + @Test + public void preloaded_listenerSupport() throws LibraryLoadException { + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + + NativeLoader loader = NativeLoader.builder().preloaded("preloaded1", "preloaded2").addListener(sharedListener).build(); + + // debatable - but no listener calls just for checking + assertTrue(loader.isPreloaded("preloaded1")); + assertTrue(loader.isPreloaded("preloaded2")); + + + + sharedListener.expectResolvePreloaded("preloaded1"); + sharedListener.expectLoadPreloaded("preloaded1"); + + TestLibraryLoadingListener scopedListener1 = new TestLibraryLoadingListener(). + expectResolvePreloaded("preloaded1"). + expectLoadPreloaded("preloaded1"); + + try (LibFile lib = loader.resolveDynamic("preloaded1", scopedListener1)) { + lib.load(); + } + + sharedListener.assertDone(); + scopedListener1.assertDone(); + + sharedListener.expectResolvePreloaded("preloaded2"); + sharedListener.expectLoadPreloaded("preloaded2"); + + TestLibraryLoadingListener scopedListener2 = new TestLibraryLoadingListener(). + expectResolvePreloaded("preloaded2"). + expectLoadPreloaded("preloaded2"); + + // load is just convenience for resolve & load + loader.load("preloaded2", scopedListener2); + + sharedListener.assertDone(); + scopedListener2.assertDone(); + + + sharedListener.expectResolveDynamicFailure("dne"); + + TestLibraryLoadingListener scopedListener3 = new TestLibraryLoadingListener(). + expectResolveDynamicFailure("dne"); + + // not already loaded - so passes through to underlying resolver + assertThrows(LibraryLoadException.class, () -> loader.load("dne", scopedListener3)); + + sharedListener.assertDone(); + scopedListener3.assertDone(); } @Test diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index 6742428475e..9eb0e31e5ef 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -6,6 +6,7 @@ import java.net.URL; import java.nio.file.Path; +import java.util.Collections; import java.util.LinkedList; public final class TestLibraryLoadingListener implements LibraryLoadingListener { @@ -34,6 +35,23 @@ public void onResolveDynamic( } }); } + + public TestLibraryLoadingListener expectResolvePreloaded(String expectedLibName) { + return this.addCheck( + new Check("onResolveDynamic:preloaded %s", expectedLibName) { + @Override + public void onResolveDynamic( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + URL optionalUrl) { + assertNull(optionalComponent); + assertEquals(libName, expectedLibName); + assertTrue(isPreloaded); + } + }); + } public TestLibraryLoadingListener expectResolveDynamicFailure(String expectedLibName) { return this.addCheck( @@ -65,6 +83,23 @@ public void onLoad( } }); } + + public TestLibraryLoadingListener expectLoadPreloaded(String expectedLibName) { + return this.addCheck( + new Check("onLoad:preloaded %s", expectedLibName) { + @Override + public void onLoad( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + boolean isPreloaded, + Path optionalLibPath) { + assertNull(optionalComponent); + assertEquals(libName, expectedLibName); + assertTrue(isPreloaded); + } + }); + } public TestLibraryLoadingListener expectLoad(String expectedComponent, String expectedLibName) { return this.addCheck( @@ -129,7 +164,8 @@ public TestLibraryLoadingListener copy() { } public void assertDone() { - assertTrue(this.checks.isEmpty()); + // written this way for better debugging + assertEquals(Collections.emptyList(), this.checks); } @Override From fbe0943ae0946df4a7873703ea8653e41fe73502 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 12:35:05 -0500 Subject: [PATCH 06/27] spotless --- .../CompositeLibraryLoadingListener.java | 7 +- .../datadog/nativeloader/NativeLoader.java | 39 ++++----- .../CompositeLibraryLoadingListenerTest.java | 45 +++++----- .../nativeloader/NativeLoaderBuilderTest.java | 78 +++++++---------- .../nativeloader/NativeLoaderTest.java | 83 ++++++++++--------- .../TestLibraryLoadingListener.java | 6 +- 6 files changed, 122 insertions(+), 136 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index 40f117d975e..5a8a764cc1c 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -21,9 +21,9 @@ final class CompositeLibraryLoadingListener extends SafeLibraryLoadingListener { public boolean isNop() { return this.listeners.isEmpty(); } - + int size() { - return this.listeners.size(); + return this.listeners.size(); } @Override @@ -119,7 +119,8 @@ public void onTempFileCleanup( @Override public CompositeLibraryLoadingListener join(LibraryLoadingListener... listeners) { - ArrayList combinedListeners = new ArrayList<>(this.listeners.size() + listeners.length); + ArrayList combinedListeners = + new ArrayList<>(this.listeners.size() + listeners.length); combinedListeners.addAll(this.listeners); combinedListeners.addAll(Arrays.asList(listeners)); return new CompositeLibraryLoadingListener(combinedListeners); diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 9600474237a..531f0565a97 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -188,7 +188,7 @@ Path tempDir() { public static final Builder builder() { return new Builder(); } - + private static final LibraryLoadingListener[] EMPTY_LISTENERS = {}; private final PlatformSpec defaultPlatformSpec; @@ -220,7 +220,8 @@ public void load(String libName) throws LibraryLoadException { this.loadImpl(null, libName, EMPTY_LISTENERS); } - public void load(String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { + public void load(String libName, LibraryLoadingListener... scopedListeners) + throws LibraryLoadException { this.loadImpl(null, libName, scopedListeners); } @@ -229,8 +230,8 @@ public void load(String component, String libName) throws LibraryLoadException { private void loadImpl(String component, String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { - - // scopedListeners are attached to the LibFile by resolveDynamicImpl + + // scopedListeners are attached to the LibFile by resolveDynamicImpl try (LibFile libFile = this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName, scopedListeners)) { libFile.load(); @@ -241,9 +242,10 @@ private void loadImpl(String component, String libName, LibraryLoadingListener.. public LibFile resolveDynamic(String libName) throws LibraryLoadException { return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName); } - - public LibFile resolveDynamic(String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { - return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName, scopedListeners); + + public LibFile resolveDynamic(String libName, LibraryLoadingListener... scopedListeners) + throws LibraryLoadException { + return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName, scopedListeners); } /** Resolves a library with an associated component */ @@ -268,14 +270,11 @@ public LibFile resolveDynamic(PlatformSpec platformSpec, String component, Strin throws LibraryLoadException { return this.resolveDynamicImpl(platformSpec, component, libName); } - + private LibFile resolveDynamicImpl( - PlatformSpec platformSpec, - String optionalComponent, - String libName) - throws LibraryLoadException - { - return this.resolveDynamicImpl(platformSpec, optionalComponent, libName, EMPTY_LISTENERS); + PlatformSpec platformSpec, String optionalComponent, String libName) + throws LibraryLoadException { + return this.resolveDynamicImpl(platformSpec, optionalComponent, libName, EMPTY_LISTENERS); } private LibFile resolveDynamicImpl( @@ -284,10 +283,12 @@ private LibFile resolveDynamicImpl( String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { - SafeLibraryLoadingListener allListeners = - (scopedListeners == null || scopedListeners == EMPTY_LISTENERS || scopedListeners.length == 0) ? - this.listeners : - this.listeners.join(scopedListeners); + SafeLibraryLoadingListener allListeners = + (scopedListeners == null + || scopedListeners == EMPTY_LISTENERS + || scopedListeners.length == 0) + ? this.listeners + : this.listeners.join(scopedListeners); if (platformSpec.isUnknownOs() || platformSpec.isUnknownArch()) { LibraryLoadException ex = new LibraryLoadException(libName, "Unsupported platform"); @@ -297,7 +298,7 @@ private LibFile resolveDynamicImpl( boolean isPreloaded = this.isPreloaded(platformSpec, libName); if (isPreloaded) { - allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, null); + allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, null); return LibFile.preloaded(platformSpec, optionalComponent, libName, allListeners); } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java index 09d962b63d1..11df313d116 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java @@ -107,33 +107,28 @@ public void onTempFileCleanup() { listener1.assertDone(); listener2.assertDone(); } - + @Test public void join() { - TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener(). - expectLoad("foo"); - - CompositeLibraryLoadingListener composite = new CompositeLibraryLoadingListener(listener1); - assertEquals(1, composite.size()); - - TestLibraryLoadingListener listener2 = listener1.copy(); - - CompositeLibraryLoadingListener composite2 = composite.join(listener2); - assertEquals(2, composite2.size()); - - TestLibraryLoadingListener listener3 = listener1.copy(); - TestLibraryLoadingListener listener4 = listener1.copy(); - - CompositeLibraryLoadingListener finalComposite = composite2.join(listener3, listener4); - assertEquals(4, finalComposite.size()); - - finalComposite.onLoad( - PlatformSpec.defaultPlatformSpec(), - null, - "foo", - false, - Paths.get("/tmp/foo.dll")); - + TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener().expectLoad("foo"); + + CompositeLibraryLoadingListener composite = new CompositeLibraryLoadingListener(listener1); + assertEquals(1, composite.size()); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + CompositeLibraryLoadingListener composite2 = composite.join(listener2); + assertEquals(2, composite2.size()); + + TestLibraryLoadingListener listener3 = listener1.copy(); + TestLibraryLoadingListener listener4 = listener1.copy(); + + CompositeLibraryLoadingListener finalComposite = composite2.join(listener3, listener4); + assertEquals(4, finalComposite.size()); + + finalComposite.onLoad( + PlatformSpec.defaultPlatformSpec(), null, "foo", false, Paths.get("/tmp/foo.dll")); + listener1.assertDone(); listener2.assertDone(); listener3.assertDone(); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderBuilderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderBuilderTest.java index b638059a966..db5a25b4541 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderBuilderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderBuilderTest.java @@ -25,59 +25,45 @@ public void customPlatformSpec() { NativeLoader.Builder builder = NativeLoader.builder().platformSpec(platformSpec); assertSame(platformSpec, builder.platformSpec()); } - + @Test public void defaultListeners() { - NativeLoader.Builder builder = NativeLoader.builder(); - - assertTrue(builder.listeners().isNop()); + NativeLoader.Builder builder = NativeLoader.builder(); + + assertTrue(builder.listeners().isNop()); } - + @Test public void addListener() { - TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener(). - expectLoad("foo"); - TestLibraryLoadingListener listener2 = listener1.copy(); - - NativeLoader.Builder builder = NativeLoader.builder(). - addListener(listener1). - addListener(listener2); - - SafeLibraryLoadingListener listener = builder.listeners(); - assertFalse(listener.isNop()); - - listener.onLoad( - builder.platformSpec(), - null, - "foo", - false, - Paths.get("/tmp/foo.dylib")); - - listener1.assertDone(); - listener2.assertDone(); - } - + TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener().expectLoad("foo"); + TestLibraryLoadingListener listener2 = listener1.copy(); + + NativeLoader.Builder builder = + NativeLoader.builder().addListener(listener1).addListener(listener2); + + SafeLibraryLoadingListener listener = builder.listeners(); + assertFalse(listener.isNop()); + + listener.onLoad(builder.platformSpec(), null, "foo", false, Paths.get("/tmp/foo.dylib")); + + listener1.assertDone(); + listener2.assertDone(); + } + @Test public void addListeners() { - TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener(). - expectLoad("foo"); - TestLibraryLoadingListener listener2 = listener1.copy(); - - NativeLoader.Builder builder = NativeLoader.builder(). - addListeners(listener1, listener2); - - SafeLibraryLoadingListener listener = builder.listeners(); - assertFalse(listener.isNop()); - - listener.onLoad( - builder.platformSpec(), - null, - "foo", - false, - Paths.get("/tmp/foo.dylib")); - - listener1.assertDone(); - listener2.assertDone(); + TestLibraryLoadingListener listener1 = new TestLibraryLoadingListener().expectLoad("foo"); + TestLibraryLoadingListener listener2 = listener1.copy(); + + NativeLoader.Builder builder = NativeLoader.builder().addListeners(listener1, listener2); + + SafeLibraryLoadingListener listener = builder.listeners(); + assertFalse(listener.isNop()); + + listener.onLoad(builder.platformSpec(), null, "foo", false, Paths.get("/tmp/foo.dylib")); + + listener1.assertDone(); + listener2.assertDone(); } @Test diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index e879d7b5fe3..575cc25380b 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -50,55 +50,58 @@ public void preloaded() throws LibraryLoadException { // not already loaded - so passes through to underlying resolver assertThrows(LibraryLoadException.class, () -> loader.load("dne")); } - + @Test public void preloaded_listenerSupport() throws LibraryLoadException { - TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); - - NativeLoader loader = NativeLoader.builder().preloaded("preloaded1", "preloaded2").addListener(sharedListener).build(); + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + + NativeLoader loader = + NativeLoader.builder() + .preloaded("preloaded1", "preloaded2") + .addListener(sharedListener) + .build(); - // debatable - but no listener calls just for checking + // debatable - but no listener calls just for checking assertTrue(loader.isPreloaded("preloaded1")); assertTrue(loader.isPreloaded("preloaded2")); - - - + sharedListener.expectResolvePreloaded("preloaded1"); sharedListener.expectLoadPreloaded("preloaded1"); - - TestLibraryLoadingListener scopedListener1 = new TestLibraryLoadingListener(). - expectResolvePreloaded("preloaded1"). - expectLoadPreloaded("preloaded1"); - - try (LibFile lib = loader.resolveDynamic("preloaded1", scopedListener1)) { - lib.load(); - } - - sharedListener.assertDone(); - scopedListener1.assertDone(); - - sharedListener.expectResolvePreloaded("preloaded2"); - sharedListener.expectLoadPreloaded("preloaded2"); - - TestLibraryLoadingListener scopedListener2 = new TestLibraryLoadingListener(). - expectResolvePreloaded("preloaded2"). - expectLoadPreloaded("preloaded2"); - - // load is just convenience for resolve & load - loader.load("preloaded2", scopedListener2); - - sharedListener.assertDone(); - scopedListener2.assertDone(); - - - sharedListener.expectResolveDynamicFailure("dne"); - - TestLibraryLoadingListener scopedListener3 = new TestLibraryLoadingListener(). - expectResolveDynamicFailure("dne"); - + + TestLibraryLoadingListener scopedListener1 = + new TestLibraryLoadingListener() + .expectResolvePreloaded("preloaded1") + .expectLoadPreloaded("preloaded1"); + + try (LibFile lib = loader.resolveDynamic("preloaded1", scopedListener1)) { + lib.load(); + } + + sharedListener.assertDone(); + scopedListener1.assertDone(); + + sharedListener.expectResolvePreloaded("preloaded2"); + sharedListener.expectLoadPreloaded("preloaded2"); + + TestLibraryLoadingListener scopedListener2 = + new TestLibraryLoadingListener() + .expectResolvePreloaded("preloaded2") + .expectLoadPreloaded("preloaded2"); + + // load is just convenience for resolve & load + loader.load("preloaded2", scopedListener2); + + sharedListener.assertDone(); + scopedListener2.assertDone(); + + sharedListener.expectResolveDynamicFailure("dne"); + + TestLibraryLoadingListener scopedListener3 = + new TestLibraryLoadingListener().expectResolveDynamicFailure("dne"); + // not already loaded - so passes through to underlying resolver assertThrows(LibraryLoadException.class, () -> loader.load("dne", scopedListener3)); - + sharedListener.assertDone(); scopedListener3.assertDone(); } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index 9eb0e31e5ef..cac65f71308 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -35,7 +35,7 @@ public void onResolveDynamic( } }); } - + public TestLibraryLoadingListener expectResolvePreloaded(String expectedLibName) { return this.addCheck( new Check("onResolveDynamic:preloaded %s", expectedLibName) { @@ -83,7 +83,7 @@ public void onLoad( } }); } - + public TestLibraryLoadingListener expectLoadPreloaded(String expectedLibName) { return this.addCheck( new Check("onLoad:preloaded %s", expectedLibName) { @@ -164,7 +164,7 @@ public TestLibraryLoadingListener copy() { } public void assertDone() { - // written this way for better debugging + // written this way for better debugging assertEquals(Collections.emptyList(), this.checks); } From ed518d1ae032572f5f07706ab41e9813310ae30b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 12:59:07 -0500 Subject: [PATCH 07/27] More end-to-end listener tests --- .../datadog/nativeloader/NativeLoader.java | 4 +++ .../nativeloader/NativeLoaderTest.java | 35 ++++++++++++++++--- .../TestLibraryLoadingListener.java | 15 ++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 531f0565a97..3517669f48a 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -205,6 +205,10 @@ private NativeLoader(Builder builder) { this.tempDir = builder.tempDir(); } + public boolean isPlatformSupported() { + return !this.defaultPlatformSpec.isUnknownOs() && !this.defaultPlatformSpec.isUnknownOs(); + } + /** Indicates if a library is considered "pre-loaded" */ public boolean isPreloaded(String libName) { return this.libResolver.isPreloaded(this.defaultPlatformSpec, libName); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 575cc25380b..8ffb504dffc 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -4,6 +4,7 @@ import static datadog.nativeloader.TestPlatformSpec.LINUX; import static datadog.nativeloader.TestPlatformSpec.UNSUPPORTED_ARCH; import static datadog.nativeloader.TestPlatformSpec.UNSUPPORTED_OS; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -108,10 +109,18 @@ public void preloaded_listenerSupport() throws LibraryLoadException { @Test public void unsupportedPlatform() { + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + PlatformSpec unsupportedOsSpec = TestPlatformSpec.of(UNSUPPORTED_OS, AARCH64); - NativeLoader loader = NativeLoader.builder().platformSpec(unsupportedOsSpec).build(); + NativeLoader loader = + NativeLoader.builder().platformSpec(unsupportedOsSpec).addListener(sharedListener).build(); + sharedListener.expectResolveDynamicFailure("dummy"); + + // short-circuit fails during resolution because os isn't supported assertThrows(LibraryLoadException.class, () -> loader.resolveDynamic("dummy")); + + sharedListener.assertDone(); } @Test @@ -119,17 +128,35 @@ public void unsupportArch() { PlatformSpec unsupportedOsSpec = TestPlatformSpec.of(LINUX, UNSUPPORTED_ARCH); NativeLoader loader = NativeLoader.builder().platformSpec(unsupportedOsSpec).build(); - assertThrows(LibraryLoadException.class, () -> loader.resolveDynamic("dummy")); + TestLibraryLoadingListener scopedListener = + new TestLibraryLoadingListener().expectResolveDynamicFailure("dummy"); + + // short-circuit fails during resolution because arch isn't supported + assertThrows(LibraryLoadException.class, () -> loader.resolveDynamic("dummy", scopedListener)); + + scopedListener.assertDone(); } @Test public void loadFailure() throws LibraryLoadException { - NativeLoader loader = NativeLoader.builder().build(); + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + + NativeLoader loader = NativeLoader.builder().addListener(sharedListener).build(); + assumeTrue(loader.isPlatformSupported()); + + sharedListener.expectResolveDynamic("dummy"); + sharedListener.expectLoadFailure("dummy"); + + TestLibraryLoadingListener scopedListener = + new TestLibraryLoadingListener().expectResolveDynamic("dummy").expectLoadFailure("dummy"); // test libraries are just text files, so they shouldn't load & link properly // NativeLoader is supposed to wrap the loading failures, so that we // remember to handle them - assertThrows(LibraryLoadException.class, () -> loader.load("dummy")); + + // on supported platforms, there is a dummy library file, so this will resolve but fail to load + // & link + assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); } @Test diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index cac65f71308..51872f1c8ae 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -84,6 +84,21 @@ public void onLoad( }); } + public TestLibraryLoadingListener expectLoadFailure(String expectedLibName) { + return this.addCheck( + new Check("onLoadFailure %s", expectedLibName) { + @Override + public void onLoadFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + LibraryLoadException optionalCause) { + assertNull(optionalComponent); + assertEquals(libName, expectedLibName); + } + }); + } + public TestLibraryLoadingListener expectLoadPreloaded(String expectedLibName) { return this.addCheck( new Check("onLoad:preloaded %s", expectedLibName) { From b376954a23a12d20a49d4e038a67f3cc9e0f5576 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 14:04:03 -0500 Subject: [PATCH 08/27] More end-to-end listener testing - checking platformSpec overrides --- .../datadog/nativeloader/NativeLoader.java | 5 ++ .../nativeloader/NativeLoaderTest.java | 17 ++++- .../TestLibraryLoadingListener.java | 62 ++++++++++++++++++- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 3517669f48a..3671ef46ba2 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -266,6 +266,11 @@ public LibFile resolveDynamic(PlatformSpec platformSpec, String libName) return this.resolveDynamicImpl(platformSpec, null, libName); } + public LibFile resolveDynamic(PlatformSpec platformSpec, String libName, LibraryLoadingListener... scopedListeners) + throws LibraryLoadException { + return this.resolveDynamicImpl(platformSpec, null, libName, scopedListeners); + } + /** * Resolves a library with an associated component with a different {@link PlatformSpec} than the * default diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 8ffb504dffc..46806655f1e 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -174,24 +174,35 @@ public void fromDir() throws LibraryLoadException { @Test public void fromDir_override_windows() throws LibraryLoadException { - NativeLoader loader = NativeLoader.builder().fromDir("test-data").build(); - + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + + NativeLoader loader = NativeLoader.builder().fromDir("test-data").addListener(sharedListener).build(); + + sharedListener.expectResolveDynamic(TestPlatformSpec.windows(), "dummy"); + try (LibFile lib = loader.resolveDynamic(TestPlatformSpec.windows(), "dummy")) { // loaded directly from directory, so no clean-up required assertRegularFile(lib); assertTrue(lib.getAbsolutePath().endsWith("dummy.dll")); } + + sharedListener.assertDone(); } @Test public void fromDir_override_mac() throws LibraryLoadException { NativeLoader loader = NativeLoader.builder().fromDir("test-data").build(); + + TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). + expectResolveDynamic(TestPlatformSpec.mac(), "dummy"); - try (LibFile lib = loader.resolveDynamic(TestPlatformSpec.mac(), "dummy")) { + try (LibFile lib = loader.resolveDynamic(TestPlatformSpec.mac(), "dummy", scopedListener)) { // loaded directly from directory, so no clean-up required assertRegularFile(lib); assertTrue(lib.getAbsolutePath().endsWith("libdummy.dylib")); } + + scopedListener.assertDone(); } @Test diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index 51872f1c8ae..440b72e85a8 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -21,8 +21,20 @@ public final class TestLibraryLoadingListener implements LibraryLoadingListener } public TestLibraryLoadingListener expectResolveDynamic(String expectedLibName) { + return this.expectResolveDynamic(new LibCheck(expectedLibName)); + } + + public TestLibraryLoadingListener expectResolveDynamic(String expectedComponent, String expectedLibName) { + return this.expectResolveDynamic(new LibCheck(expectedComponent, expectedLibName)); + } + + public TestLibraryLoadingListener expectResolveDynamic(PlatformSpec expectedPlatformSpec, String expectedLibName) { + return this.expectResolveDynamic(new LibCheck(expectedPlatformSpec, expectedLibName)); + } + + TestLibraryLoadingListener expectResolveDynamic(LibCheck libCheck) { return this.addCheck( - new Check("onResolveDynamic %s", expectedLibName) { + new Check("onResolveDynamic %s", libCheck) { @Override public void onResolveDynamic( PlatformSpec platformSpec, @@ -30,8 +42,7 @@ public void onResolveDynamic( String libName, boolean isPreloaded, URL optionalUrl) { - assertNull(optionalComponent); - assertEquals(libName, expectedLibName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); } }); } @@ -263,6 +274,51 @@ TestLibraryLoadingListener addCheck(Check check) { Check nextCheck() { return this.checks.isEmpty() ? Check.NOTHING : this.checks.removeFirst(); } + + static final class LibCheck { + private final PlatformSpec expectedPlatformSpec; + private final String expectedComponent; + private final String expectedLibName; + + LibCheck(PlatformSpec expectedPlatformSpec, String expectedLibName) { + this(null, null, expectedLibName); + } + + LibCheck(String expectedComponent, String expectedLibName) { + this(null, expectedComponent, expectedLibName); + } + + LibCheck(String expectedLibName) { + this(null, null, expectedLibName); + } + + LibCheck(PlatformSpec expectedPlatformSpec, String expectedComponent, String expectedLibName) { + this.expectedPlatformSpec = expectedPlatformSpec; + this.expectedComponent = expectedComponent; + this.expectedLibName = expectedLibName; + } + + void assertMatches(PlatformSpec platformSpec, String optionalComponent, String libName) { + if ( this.expectedPlatformSpec != null ) { + assertEquals(this.expectedPlatformSpec, platformSpec); + } + if ( this.expectedComponent == null ) { + assertNull(optionalComponent); + } else { + assertEquals(this.expectedComponent, optionalComponent); + } + assertEquals(this.expectedLibName, libName); + } + + @Override + public String toString() { + if ( this.expectedComponent == null ) { + return this.expectedLibName; + } else { + return this.expectedComponent + "/" + this.expectedLibName; + } + } + } abstract static class Check implements LibraryLoadingListener { static final Check NOTHING = new Check("nothing") {}; From 01ef204e64a16db3effee8fc2c1589ff40cb3f13 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 15:21:50 -0500 Subject: [PATCH 09/27] More end-to-end listener tests - checking component support --- .../java/datadog/nativeloader/NativeLoaderTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 46806655f1e..6fcc9ccde06 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -228,17 +228,27 @@ public void fromDirList() throws LibraryLoadException { @Test public void fromDir_with_component() throws LibraryLoadException { - NativeLoader loader = NativeLoader.builder().fromDir("test-data").build(); + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + + NativeLoader loader = NativeLoader.builder().fromDir("test-data").addListener(sharedListener).build(); + + sharedListener.expectResolveDynamic("comp1", "dummy"); try (LibFile lib = loader.resolveDynamic("comp1", "dummy")) { assertRegularFile(lib); assertTrue(lib.getAbsolutePath().contains("comp1")); } + + sharedListener.assertDone(); + + sharedListener.expectResolveDynamic("comp2", "dummy"); try (LibFile lib = loader.resolveDynamic("comp2", "dummy")) { assertRegularFile(lib); assertTrue(lib.getAbsolutePath().contains("comp2")); } + + sharedListener.assertDone(); } @Test From cd6affc1801965cc815deb679b7d0f2cc41bdbec Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 16:40:17 -0500 Subject: [PATCH 10/27] More end-to-end listener tests - added tests for temporary file creation - exposed a problem with TestLibraryLoadingListener errors getting swallowed in CompositeLibraryLoadingListener - adding failure tracking into TestLibraryLoadingListener that is checked in assertDone --- .../CompositeLibraryLoadingListener.java | 5 ++ .../java/datadog/nativeloader/LibFile.java | 4 +- .../datadog/nativeloader/NativeLoader.java | 23 +++--- .../CompositeLibraryLoadingListenerTest.java | 3 +- .../nativeloader/NativeLoaderTest.java | 12 +++- .../TestLibraryLoadingListener.java | 70 +++++++++++++------ 6 files changed, 80 insertions(+), 37 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index 5a8a764cc1c..e425853fc6f 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -125,4 +125,9 @@ public CompositeLibraryLoadingListener join(LibraryLoadingListener... listeners) combinedListeners.addAll(Arrays.asList(listeners)); return new CompositeLibraryLoadingListener(combinedListeners); } + + @Override + public String toString() { + return this.getClass().getSimpleName() + ":" + this.listeners.toString(); + } } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java b/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java index 866ec89601d..2db1bb8daba 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java @@ -118,8 +118,8 @@ public final String getAbsolutePath() { @Override public void close() { if (this.needsCleanup) { - boolean done = NativeLoader.delete(this.optionalFile); - if (done) { + boolean deleted = NativeLoader.delete(this.optionalFile); + if (deleted) { this.listeners.onTempFileCleanup( this.platformSpec, this.optionalComponent, this.libName, this.optionalFile.toPath()); } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 3671ef46ba2..6c1be3c201e 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -331,35 +331,40 @@ private LibFile resolveDynamicImpl( } allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, url); - return this.toLibFile(platformSpec, optionalComponent, libName, url, allListeners); + return toLibFile(this.tempDir, platformSpec, optionalComponent, libName, url, allListeners); } - private LibFile toLibFile( - PlatformSpec platformSpec, + private static LibFile toLibFile( + Path tempDir, + PlatformSpec platformSpec, String optionalComponent, String libName, URL url, - SafeLibraryLoadingListener listeners) + SafeLibraryLoadingListener allListeners) throws LibraryLoadException { if (url.getProtocol().equals("file")) { return LibFile.fromFile( - platformSpec, optionalComponent, libName, new File(url.getPath()), listeners); + platformSpec, optionalComponent, libName, new File(url.getPath()), allListeners); } else { String libExt = PathUtils.dynamicLibExtension(platformSpec); Path tempFile = null; try { - tempFile = TempFileHelper.createTempFile(this.tempDir, libName, libExt); + tempFile = TempFileHelper.createTempFile(tempDir, libName, libExt); try (InputStream in = url.openStream()) { Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING); } + allListeners.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); + return LibFile.fromTempFile( - platformSpec, optionalComponent, libName, tempFile.toFile(), listeners); + platformSpec, optionalComponent, libName, tempFile.toFile(), allListeners); + } catch (Throwable t) { - listeners.onTempFileCreationFailure( - platformSpec, optionalComponent, libName, this.tempDir, libExt, tempFile, t); + allListeners.onTempFileCreationFailure( + platformSpec, optionalComponent, libName, tempDir, libExt, tempFile, t); + throw new LibraryLoadException(libName, t); } } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java index 11df313d116..1a40f6a100f 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java @@ -50,8 +50,7 @@ public void onLoad() { TestLibraryLoadingListener listener2 = listener1.copy(); listeners(listener1, listener2) - .onResolveDynamicFailure( - PlatformSpec.defaultPlatformSpec(), null, "foo", new LibraryLoadException("foo")); + .onLoad(PlatformSpec.defaultPlatformSpec(), null, "foo", false, null); listener1.assertDone(); listener2.assertDone(); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 6fcc9ccde06..196c456b50e 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -320,11 +320,19 @@ public void fromJarBackedClassLoader() throws IOException, LibraryLoadException Path jar = jar("test-data"); try { try (URLClassLoader classLoader = createClassLoader(jar)) { - NativeLoader loader = NativeLoader.builder().fromClassLoader(classLoader).build(); - try (LibFile lib = loader.resolveDynamic("dummy")) { + NativeLoader loader = NativeLoader.builder().fromClassLoader(classLoader).build(); + + TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). + expectResolveDynamic("dummy"). + expectTempFileCreated("dummy"). + expectTempFileCleanup("dummy"); + + try (LibFile lib = loader.resolveDynamic("dummy", scopedListener)) { // loaded from a jar, so copied to temp file assertTempFile(lib); } + + scopedListener.assertDone(); } } finally { deleteHelper(jar); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index 440b72e85a8..76555263b59 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.net.URL; import java.nio.file.Path; @@ -11,6 +12,9 @@ public final class TestLibraryLoadingListener implements LibraryLoadingListener { private final LinkedList checks; + + private Check failedCheck = null; + private Throwable failedCause = null; TestLibraryLoadingListener() { this.checks = new LinkedList<>(); @@ -190,6 +194,16 @@ public TestLibraryLoadingListener copy() { } public void assertDone() { + if ( this.failedCheck != null ) { + try { + fail("check failed: " + this.failedCheck, this.failedCause); + } catch ( AssertionError e ) { + e.initCause(this.failedCause); + + throw e; + } + } + // written this way for better debugging assertEquals(Collections.emptyList(), this.checks); } @@ -201,8 +215,7 @@ public void onResolveDynamic( String libName, boolean isPreloaded, URL optionalUrl) { - this.nextCheck() - .onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, optionalUrl); + this.nextCheck(check -> check.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, optionalUrl)); } @Override @@ -211,8 +224,7 @@ public void onResolveDynamicFailure( String optionalComponent, String libName, LibraryLoadException optionalCause) { - this.nextCheck() - .onResolveDynamicFailure(platformSpec, optionalComponent, libName, optionalCause); + this.nextCheck(check -> check.onResolveDynamicFailure(platformSpec, optionalComponent, libName, optionalCause)); } @Override @@ -222,7 +234,7 @@ public void onLoad( String libName, boolean isPreloaded, Path optionalLibPath) { - this.nextCheck().onLoad(platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath); + this.nextCheck(check -> check.onLoad(platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath)); } @Override @@ -231,13 +243,15 @@ public void onLoadFailure( String optionalComponent, String libName, LibraryLoadException optionalCause) { - this.nextCheck().onLoadFailure(platformSpec, optionalComponent, libName, optionalCause); + this.nextCheck(check -> check.onLoadFailure(platformSpec, optionalComponent, libName, optionalCause)); } @Override public void onTempFileCreated( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - this.nextCheck().onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); + if ( true ) new RuntimeException("onTempFileCreated!"); + + this.nextCheck(check -> check.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile)); } @Override @@ -249,21 +263,20 @@ public void onTempFileCreationFailure( String libExt, Path optionalTempFile, Throwable optionalCause) { - this.nextCheck() - .onTempFileCreationFailure( + this.nextCheck(check -> check.onTempFileCreationFailure( platformSpec, optionalComponent, libName, tempDir, libExt, optionalTempFile, - optionalCause); + optionalCause)); } @Override public void onTempFileCleanup( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - this.nextCheck().onTempFileCleanup(platformSpec, optionalComponent, libName, tempFile); + this.nextCheck(check -> check.onTempFileCleanup(platformSpec, optionalComponent, libName, tempFile)); } TestLibraryLoadingListener addCheck(Check check) { @@ -271,8 +284,16 @@ TestLibraryLoadingListener addCheck(Check check) { return this; } - Check nextCheck() { - return this.checks.isEmpty() ? Check.NOTHING : this.checks.removeFirst(); + void nextCheck(CheckInvocation invocation) { + Check nextCheck = this.checks.isEmpty() ? Check.NOTHING : this.checks.removeFirst(); + try { + invocation.invoke(nextCheck); + } catch ( Throwable t ) { + if ( this.failedCheck == null ) { + this.failedCheck = nextCheck; + this.failedCause = t; + } + } } static final class LibCheck { @@ -319,6 +340,11 @@ public String toString() { } } } + + @FunctionalInterface + interface CheckInvocation { + void invoke(Check check); + } abstract static class Check implements LibraryLoadingListener { static final Check NOTHING = new Check("nothing") {}; @@ -340,7 +366,7 @@ public void onLoad( String libName, boolean isPreloaded, Path optionalLibPath) { - this.fail("onLoad", platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath); + this.fallback("onLoad", platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath); } @Override @@ -349,7 +375,7 @@ public void onLoadFailure( String optionalComponent, String libName, LibraryLoadException optionalCause) { - this.fail("onLoadFailure", platformSpec, optionalComponent, libName, optionalCause); + this.fallback("onLoadFailure", platformSpec, optionalComponent, libName, optionalCause); } @Override @@ -359,7 +385,7 @@ public void onResolveDynamic( String libName, boolean isPreloaded, URL optionalUrl) { - this.fail( + this.fallback( "onResolveDynamic", platformSpec, optionalComponent, libName, isPreloaded, optionalUrl); } @@ -369,13 +395,13 @@ public void onResolveDynamicFailure( String optionalComponent, String libName, LibraryLoadException optionalCause) { - this.fail("onResolveDynamicFailure", platformSpec, optionalComponent, libName, optionalCause); + this.fallback("onResolveDynamicFailure", platformSpec, optionalComponent, libName, optionalCause); } @Override public void onTempFileCreated( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - this.fail("onTmepFileCreated", platformSpec, optionalComponent, libName, tempFile); + this.fallback("onTmepFileCreated", platformSpec, optionalComponent, libName, tempFile); } @Override @@ -387,7 +413,7 @@ public void onTempFileCreationFailure( String libExt, Path optionalTempFile, Throwable optionalCause) { - this.fail( + this.fallback( "onTempFileCreationFailure", platformSpec, optionalComponent, @@ -401,10 +427,10 @@ public void onTempFileCreationFailure( @Override public void onTempFileCleanup( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - this.fail("onTempFileCleanup", platformSpec, optionalComponent, libName, tempFile); + this.fallback("onTempFileCleanup", platformSpec, optionalComponent, libName, tempFile); } - void fail(String methodName, Object... args) { + void fallback(String methodName, Object... args) { fail("unxpected call: " + callToString(methodName, args) + " - expected: " + this.name); } @@ -414,7 +440,7 @@ static final String callToString(String methodName, Object... args) { builder.append('('); for (int i = 0; i < args.length; ++i) { if (i != 0) builder.append(", "); - builder.append(args[i].toString()); + builder.append(String.valueOf(args[i])); } builder.append(')'); return builder.toString(); From 625b8c3f4436f8109aaa53c1b037c58d8ec2bd67 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 16:42:15 -0500 Subject: [PATCH 11/27] spotless --- .../CompositeLibraryLoadingListener.java | 4 +- .../datadog/nativeloader/NativeLoader.java | 15 +- .../nativeloader/NativeLoaderTest.java | 57 +++--- .../TestLibraryLoadingListener.java | 184 ++++++++++-------- 4 files changed, 140 insertions(+), 120 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index e425853fc6f..5bf5cc509f3 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -125,9 +125,9 @@ public CompositeLibraryLoadingListener join(LibraryLoadingListener... listeners) combinedListeners.addAll(Arrays.asList(listeners)); return new CompositeLibraryLoadingListener(combinedListeners); } - + @Override public String toString() { - return this.getClass().getSimpleName() + ":" + this.listeners.toString(); + return this.getClass().getSimpleName() + ":" + this.listeners.toString(); } } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 6c1be3c201e..1bb10ba125a 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -266,11 +266,12 @@ public LibFile resolveDynamic(PlatformSpec platformSpec, String libName) return this.resolveDynamicImpl(platformSpec, null, libName); } - public LibFile resolveDynamic(PlatformSpec platformSpec, String libName, LibraryLoadingListener... scopedListeners) + public LibFile resolveDynamic( + PlatformSpec platformSpec, String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { return this.resolveDynamicImpl(platformSpec, null, libName, scopedListeners); } - + /** * Resolves a library with an associated component with a different {@link PlatformSpec} than the * default @@ -336,7 +337,7 @@ private LibFile resolveDynamicImpl( private static LibFile toLibFile( Path tempDir, - PlatformSpec platformSpec, + PlatformSpec platformSpec, String optionalComponent, String libName, URL url, @@ -357,14 +358,14 @@ private static LibFile toLibFile( } allListeners.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); - + return LibFile.fromTempFile( platformSpec, optionalComponent, libName, tempFile.toFile(), allListeners); - + } catch (Throwable t) { - allListeners.onTempFileCreationFailure( + allListeners.onTempFileCreationFailure( platformSpec, optionalComponent, libName, tempDir, libExt, tempFile, t); - + throw new LibraryLoadException(libName, t); } } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 196c456b50e..3af170ce540 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -4,12 +4,12 @@ import static datadog.nativeloader.TestPlatformSpec.LINUX; import static datadog.nativeloader.TestPlatformSpec.UNSUPPORTED_ARCH; import static datadog.nativeloader.TestPlatformSpec.UNSUPPORTED_OS; -import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.File; import java.io.IOException; @@ -175,33 +175,34 @@ public void fromDir() throws LibraryLoadException { @Test public void fromDir_override_windows() throws LibraryLoadException { TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); - - NativeLoader loader = NativeLoader.builder().fromDir("test-data").addListener(sharedListener).build(); - + + NativeLoader loader = + NativeLoader.builder().fromDir("test-data").addListener(sharedListener).build(); + sharedListener.expectResolveDynamic(TestPlatformSpec.windows(), "dummy"); - + try (LibFile lib = loader.resolveDynamic(TestPlatformSpec.windows(), "dummy")) { // loaded directly from directory, so no clean-up required assertRegularFile(lib); assertTrue(lib.getAbsolutePath().endsWith("dummy.dll")); } - + sharedListener.assertDone(); } @Test public void fromDir_override_mac() throws LibraryLoadException { NativeLoader loader = NativeLoader.builder().fromDir("test-data").build(); - - TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). - expectResolveDynamic(TestPlatformSpec.mac(), "dummy"); + + TestLibraryLoadingListener scopedListener = + new TestLibraryLoadingListener().expectResolveDynamic(TestPlatformSpec.mac(), "dummy"); try (LibFile lib = loader.resolveDynamic(TestPlatformSpec.mac(), "dummy", scopedListener)) { // loaded directly from directory, so no clean-up required assertRegularFile(lib); assertTrue(lib.getAbsolutePath().endsWith("libdummy.dylib")); } - + scopedListener.assertDone(); } @@ -228,26 +229,27 @@ public void fromDirList() throws LibraryLoadException { @Test public void fromDir_with_component() throws LibraryLoadException { - TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); - - NativeLoader loader = NativeLoader.builder().fromDir("test-data").addListener(sharedListener).build(); - + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + + NativeLoader loader = + NativeLoader.builder().fromDir("test-data").addListener(sharedListener).build(); + sharedListener.expectResolveDynamic("comp1", "dummy"); try (LibFile lib = loader.resolveDynamic("comp1", "dummy")) { assertRegularFile(lib); assertTrue(lib.getAbsolutePath().contains("comp1")); } - + sharedListener.assertDone(); - + sharedListener.expectResolveDynamic("comp2", "dummy"); try (LibFile lib = loader.resolveDynamic("comp2", "dummy")) { assertRegularFile(lib); assertTrue(lib.getAbsolutePath().contains("comp2")); } - + sharedListener.assertDone(); } @@ -320,19 +322,20 @@ public void fromJarBackedClassLoader() throws IOException, LibraryLoadException Path jar = jar("test-data"); try { try (URLClassLoader classLoader = createClassLoader(jar)) { - NativeLoader loader = NativeLoader.builder().fromClassLoader(classLoader).build(); - - TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). - expectResolveDynamic("dummy"). - expectTempFileCreated("dummy"). - expectTempFileCleanup("dummy"); - - try (LibFile lib = loader.resolveDynamic("dummy", scopedListener)) { + NativeLoader loader = NativeLoader.builder().fromClassLoader(classLoader).build(); + + TestLibraryLoadingListener scopedListener = + new TestLibraryLoadingListener() + .expectResolveDynamic("dummy") + .expectTempFileCreated("dummy") + .expectTempFileCleanup("dummy"); + + try (LibFile lib = loader.resolveDynamic("dummy", scopedListener)) { // loaded from a jar, so copied to temp file assertTempFile(lib); } - - scopedListener.assertDone(); + + scopedListener.assertDone(); } } finally { deleteHelper(jar); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index 76555263b59..e7c879229ad 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -12,7 +12,7 @@ public final class TestLibraryLoadingListener implements LibraryLoadingListener { private final LinkedList checks; - + private Check failedCheck = null; private Throwable failedCause = null; @@ -25,17 +25,19 @@ public final class TestLibraryLoadingListener implements LibraryLoadingListener } public TestLibraryLoadingListener expectResolveDynamic(String expectedLibName) { - return this.expectResolveDynamic(new LibCheck(expectedLibName)); + return this.expectResolveDynamic(new LibCheck(expectedLibName)); } - - public TestLibraryLoadingListener expectResolveDynamic(String expectedComponent, String expectedLibName) { - return this.expectResolveDynamic(new LibCheck(expectedComponent, expectedLibName)); + + public TestLibraryLoadingListener expectResolveDynamic( + String expectedComponent, String expectedLibName) { + return this.expectResolveDynamic(new LibCheck(expectedComponent, expectedLibName)); } - - public TestLibraryLoadingListener expectResolveDynamic(PlatformSpec expectedPlatformSpec, String expectedLibName) { - return this.expectResolveDynamic(new LibCheck(expectedPlatformSpec, expectedLibName)); + + public TestLibraryLoadingListener expectResolveDynamic( + PlatformSpec expectedPlatformSpec, String expectedLibName) { + return this.expectResolveDynamic(new LibCheck(expectedPlatformSpec, expectedLibName)); } - + TestLibraryLoadingListener expectResolveDynamic(LibCheck libCheck) { return this.addCheck( new Check("onResolveDynamic %s", libCheck) { @@ -194,16 +196,16 @@ public TestLibraryLoadingListener copy() { } public void assertDone() { - if ( this.failedCheck != null ) { - try { - fail("check failed: " + this.failedCheck, this.failedCause); - } catch ( AssertionError e ) { - e.initCause(this.failedCause); - - throw e; - } - } - + if (this.failedCheck != null) { + try { + fail("check failed: " + this.failedCheck, this.failedCause); + } catch (AssertionError e) { + e.initCause(this.failedCause); + + throw e; + } + } + // written this way for better debugging assertEquals(Collections.emptyList(), this.checks); } @@ -215,7 +217,10 @@ public void onResolveDynamic( String libName, boolean isPreloaded, URL optionalUrl) { - this.nextCheck(check -> check.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, optionalUrl)); + this.nextCheck( + check -> + check.onResolveDynamic( + platformSpec, optionalComponent, libName, isPreloaded, optionalUrl)); } @Override @@ -224,7 +229,9 @@ public void onResolveDynamicFailure( String optionalComponent, String libName, LibraryLoadException optionalCause) { - this.nextCheck(check -> check.onResolveDynamicFailure(platformSpec, optionalComponent, libName, optionalCause)); + this.nextCheck( + check -> + check.onResolveDynamicFailure(platformSpec, optionalComponent, libName, optionalCause)); } @Override @@ -234,7 +241,9 @@ public void onLoad( String libName, boolean isPreloaded, Path optionalLibPath) { - this.nextCheck(check -> check.onLoad(platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath)); + this.nextCheck( + check -> + check.onLoad(platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath)); } @Override @@ -243,15 +252,17 @@ public void onLoadFailure( String optionalComponent, String libName, LibraryLoadException optionalCause) { - this.nextCheck(check -> check.onLoadFailure(platformSpec, optionalComponent, libName, optionalCause)); + this.nextCheck( + check -> check.onLoadFailure(platformSpec, optionalComponent, libName, optionalCause)); } @Override public void onTempFileCreated( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - if ( true ) new RuntimeException("onTempFileCreated!"); - - this.nextCheck(check -> check.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile)); + if (true) new RuntimeException("onTempFileCreated!"); + + this.nextCheck( + check -> check.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile)); } @Override @@ -263,20 +274,23 @@ public void onTempFileCreationFailure( String libExt, Path optionalTempFile, Throwable optionalCause) { - this.nextCheck(check -> check.onTempFileCreationFailure( - platformSpec, - optionalComponent, - libName, - tempDir, - libExt, - optionalTempFile, - optionalCause)); + this.nextCheck( + check -> + check.onTempFileCreationFailure( + platformSpec, + optionalComponent, + libName, + tempDir, + libExt, + optionalTempFile, + optionalCause)); } @Override public void onTempFileCleanup( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - this.nextCheck(check -> check.onTempFileCleanup(platformSpec, optionalComponent, libName, tempFile)); + this.nextCheck( + check -> check.onTempFileCleanup(platformSpec, optionalComponent, libName, tempFile)); } TestLibraryLoadingListener addCheck(Check check) { @@ -288,62 +302,62 @@ void nextCheck(CheckInvocation invocation) { Check nextCheck = this.checks.isEmpty() ? Check.NOTHING : this.checks.removeFirst(); try { invocation.invoke(nextCheck); - } catch ( Throwable t ) { - if ( this.failedCheck == null ) { + } catch (Throwable t) { + if (this.failedCheck == null) { this.failedCheck = nextCheck; this.failedCause = t; } } } - + static final class LibCheck { - private final PlatformSpec expectedPlatformSpec; - private final String expectedComponent; - private final String expectedLibName; - - LibCheck(PlatformSpec expectedPlatformSpec, String expectedLibName) { - this(null, null, expectedLibName); - } - - LibCheck(String expectedComponent, String expectedLibName) { - this(null, expectedComponent, expectedLibName); - } - - LibCheck(String expectedLibName) { - this(null, null, expectedLibName); - } - - LibCheck(PlatformSpec expectedPlatformSpec, String expectedComponent, String expectedLibName) { - this.expectedPlatformSpec = expectedPlatformSpec; - this.expectedComponent = expectedComponent; - this.expectedLibName = expectedLibName; - } - - void assertMatches(PlatformSpec platformSpec, String optionalComponent, String libName) { - if ( this.expectedPlatformSpec != null ) { - assertEquals(this.expectedPlatformSpec, platformSpec); - } - if ( this.expectedComponent == null ) { - assertNull(optionalComponent); - } else { - assertEquals(this.expectedComponent, optionalComponent); - } - assertEquals(this.expectedLibName, libName); - } - - @Override - public String toString() { - if ( this.expectedComponent == null ) { - return this.expectedLibName; - } else { - return this.expectedComponent + "/" + this.expectedLibName; - } - } + private final PlatformSpec expectedPlatformSpec; + private final String expectedComponent; + private final String expectedLibName; + + LibCheck(PlatformSpec expectedPlatformSpec, String expectedLibName) { + this(null, null, expectedLibName); + } + + LibCheck(String expectedComponent, String expectedLibName) { + this(null, expectedComponent, expectedLibName); + } + + LibCheck(String expectedLibName) { + this(null, null, expectedLibName); + } + + LibCheck(PlatformSpec expectedPlatformSpec, String expectedComponent, String expectedLibName) { + this.expectedPlatformSpec = expectedPlatformSpec; + this.expectedComponent = expectedComponent; + this.expectedLibName = expectedLibName; + } + + void assertMatches(PlatformSpec platformSpec, String optionalComponent, String libName) { + if (this.expectedPlatformSpec != null) { + assertEquals(this.expectedPlatformSpec, platformSpec); + } + if (this.expectedComponent == null) { + assertNull(optionalComponent); + } else { + assertEquals(this.expectedComponent, optionalComponent); + } + assertEquals(this.expectedLibName, libName); + } + + @Override + public String toString() { + if (this.expectedComponent == null) { + return this.expectedLibName; + } else { + return this.expectedComponent + "/" + this.expectedLibName; + } + } } - + @FunctionalInterface interface CheckInvocation { - void invoke(Check check); + void invoke(Check check); } abstract static class Check implements LibraryLoadingListener { @@ -366,7 +380,8 @@ public void onLoad( String libName, boolean isPreloaded, Path optionalLibPath) { - this.fallback("onLoad", platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath); + this.fallback( + "onLoad", platformSpec, optionalComponent, libName, isPreloaded, optionalLibPath); } @Override @@ -395,7 +410,8 @@ public void onResolveDynamicFailure( String optionalComponent, String libName, LibraryLoadException optionalCause) { - this.fallback("onResolveDynamicFailure", platformSpec, optionalComponent, libName, optionalCause); + this.fallback( + "onResolveDynamicFailure", platformSpec, optionalComponent, libName, optionalCause); } @Override From 2b2b9f92c0955db0b497e6ad75698d2e126a2bb5 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 16:50:05 -0500 Subject: [PATCH 12/27] Fixed errant text replacement in Javadoc --- .../src/main/java/datadog/nativeloader/LibFile.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java b/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java index 2db1bb8daba..352abae8e76 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java @@ -7,9 +7,9 @@ * Represents a resolved library * *
    - *
  • library may be preloaded - with no backing optionalFile - *
  • regular optionalFile - that doesn't require clean-up - *
  • temporary optionalFile - copying from another source - that does require clean-up + *
  • library may be preloaded - with no backing file + *
  • regular file - that doesn't require clean-up + *
  • temporary file - copying from another source - that does require clean-up *
*/ public final class LibFile implements AutoCloseable { From b00bbb6c991ebecdb59a4945c8510ed81930d7e3 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 5 Nov 2025 16:56:53 -0500 Subject: [PATCH 13/27] Adding clarifying comments --- .../java/datadog/nativeloader/LibraryLoadingListener.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index 7a3e86bd7cb..ae75e7eb4cd 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -46,8 +46,16 @@ default void onTempFileCleanup( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) {} } +/** + * "safe" listeners are used inside NativeLoader to avoid exceptions leaking out + * + *

The "safe" listeners are {@link CompositeLibraryLoadingListener} used to wrap regular + * listeners and {@link NopLibraryLoadingListener} used to optimize the nop case. + */ abstract class SafeLibraryLoadingListener implements LibraryLoadingListener { + /** Used to create a new safe listener with the provided listeners append onto this one */ public abstract SafeLibraryLoadingListener join(LibraryLoadingListener... listeners); + /** Indicates if all listener operates are nops */ public abstract boolean isNop(); } From 140ae13c1a1ad1fda3ebbae4adc1d1c51e3386ac Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 10:03:48 -0500 Subject: [PATCH 14/27] Tweaking listener API - passing underlying cause rather than LibraryLoadException - adding more implied assertions to TestLibraryLoadingListener - using LibCheck throughout TestLibraryLoadingListener --- .../CompositeLibraryLoadingListener.java | 2 +- .../nativeloader/LibraryLoadingListener.java | 14 +- .../datadog/nativeloader/NativeLoader.java | 16 +- .../TestLibraryLoadingListener.java | 235 ++++++++++-------- .../ThrowingLibraryLoadingListener.java | 4 +- 5 files changed, 153 insertions(+), 118 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index 5bf5cc509f3..a22f5bf9add 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -47,7 +47,7 @@ public void onResolveDynamicFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) { + Throwable optionalCause) { for (LibraryLoadingListener listener : this.listeners) { try { listener.onResolveDynamicFailure(platformSpec, optionalComponent, libName, optionalCause); diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index ae75e7eb4cd..dc5667afcd6 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -4,6 +4,12 @@ import java.nio.file.Path; public interface LibraryLoadingListener { + /** + * Called when a dynamic library is "resolved" + * This includes an attempt to resolve a pre-loaded library + * + * If the library is pre-loaded optionalUrl will be null + */ default void onResolveDynamic( PlatformSpec platformSpec, String optionalComponent, @@ -11,11 +17,15 @@ default void onResolveDynamic( boolean isPreloaded, URL optionalUrl) {} + /** + * Called when a dynamic library fails to "resolve" + * This can occur because the library was not found -- or an exception occurred during resolution + */ default void onResolveDynamicFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) {} + Throwable optionalCause) {} default void onLoad( PlatformSpec platformSpec, @@ -28,7 +38,7 @@ default void onLoadFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) {} + Throwable optionalCause) {} default void onTempFileCreated( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) {} diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 1bb10ba125a..1eae3792d8d 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -189,6 +189,8 @@ public static final Builder builder() { return new Builder(); } + private static final URL NO_URL = null; + private static final Throwable NO_CAUSE = null; private static final LibraryLoadingListener[] EMPTY_LISTENERS = {}; private final PlatformSpec defaultPlatformSpec; @@ -301,14 +303,13 @@ private LibFile resolveDynamicImpl( : this.listeners.join(scopedListeners); if (platformSpec.isUnknownOs() || platformSpec.isUnknownArch()) { - LibraryLoadException ex = new LibraryLoadException(libName, "Unsupported platform"); - allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); - throw ex; + allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, NO_CAUSE); + throw new LibraryLoadException(libName, "Unsupported platform"); } boolean isPreloaded = this.isPreloaded(platformSpec, libName); if (isPreloaded) { - allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, null); + allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, NO_URL); return LibFile.preloaded(platformSpec, optionalComponent, libName, allListeners); } @@ -317,12 +318,11 @@ private LibFile resolveDynamicImpl( url = this.libResolver.resolve(this.pathResolver, optionalComponent, platformSpec, libName); } catch (LibraryLoadException e) { // don't wrap if it is already a LibraryLoadException - allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, e); + allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, e.getCause()); throw e; } catch (Throwable t) { - LibraryLoadException ex = new LibraryLoadException(libName, t); - allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); - throw ex; + allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, t); + throw new LibraryLoadException(libName, t); } if (url == null) { diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index e7c879229ad..e54a765b9f0 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -1,6 +1,7 @@ package datadog.nativeloader; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -16,11 +17,11 @@ public final class TestLibraryLoadingListener implements LibraryLoadingListener private Check failedCheck = null; private Throwable failedCause = null; - TestLibraryLoadingListener() { + public TestLibraryLoadingListener() { this.checks = new LinkedList<>(); } - TestLibraryLoadingListener(TestLibraryLoadingListener that) { + private TestLibraryLoadingListener(TestLibraryLoadingListener that) { this.checks = new LinkedList<>(that.checks); } @@ -38,7 +39,7 @@ public TestLibraryLoadingListener expectResolveDynamic( return this.expectResolveDynamic(new LibCheck(expectedPlatformSpec, expectedLibName)); } - TestLibraryLoadingListener expectResolveDynamic(LibCheck libCheck) { + private TestLibraryLoadingListener expectResolveDynamic(LibCheck libCheck) { return this.addCheck( new Check("onResolveDynamic %s", libCheck) { @Override @@ -54,8 +55,12 @@ public void onResolveDynamic( } public TestLibraryLoadingListener expectResolvePreloaded(String expectedLibName) { + return this.expectResolvePreloaded(new LibCheck(expectedLibName)); + } + + private TestLibraryLoadingListener expectResolvePreloaded(LibCheck libCheck) { return this.addCheck( - new Check("onResolveDynamic:preloaded %s", expectedLibName) { + new Check("onResolveDynamic:preloaded %s", libCheck) { @Override public void onResolveDynamic( PlatformSpec platformSpec, @@ -63,31 +68,41 @@ public void onResolveDynamic( String libName, boolean isPreloaded, URL optionalUrl) { - assertNull(optionalComponent); - assertEquals(libName, expectedLibName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); assertTrue(isPreloaded); } }); } public TestLibraryLoadingListener expectResolveDynamicFailure(String expectedLibName) { + return this.expectResolveDynamicFailure(new LibCheck(expectedLibName)); + } + + private TestLibraryLoadingListener expectResolveDynamicFailure(LibCheck libCheck) { return this.addCheck( - new Check("onResolveDynamicFailure %s", expectedLibName) { + new Check("onResolveDynamicFailure %s", libCheck) { @Override public void onResolveDynamicFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) { - assertNull(optionalComponent); - assertEquals(libName, expectedLibName); + Throwable optionalCause) { + libCheck.assertMatches(platformSpec, optionalComponent, libName); } }); } public TestLibraryLoadingListener expectLoad(String expectedLibName) { + return this.expectLoad(new LibCheck(expectedLibName)); + } + + public TestLibraryLoadingListener expectLoad(String expectedComponent, String expectedLibName) { + return this.expectLoad(new LibCheck(expectedComponent, expectedLibName)); + } + + private TestLibraryLoadingListener expectLoad(LibCheck libCheck) { return this.addCheck( - new Check("onLoad %s", expectedLibName) { + new Check("onLoad %s", libCheck) { @Override public void onLoad( PlatformSpec platformSpec, @@ -95,30 +110,19 @@ public void onLoad( String libName, boolean isPreloaded, Path optionalLibPath) { - assertNull(optionalComponent); - assertEquals(libName, expectedLibName); - } - }); - } - - public TestLibraryLoadingListener expectLoadFailure(String expectedLibName) { - return this.addCheck( - new Check("onLoadFailure %s", expectedLibName) { - @Override - public void onLoadFailure( - PlatformSpec platformSpec, - String optionalComponent, - String libName, - LibraryLoadException optionalCause) { - assertNull(optionalComponent); - assertEquals(libName, expectedLibName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); } }); } + public TestLibraryLoadingListener expectLoadPreloaded(String expectedLibName) { + return this.expectLoadPreloaded(new LibCheck(expectedLibName)); + } + + private TestLibraryLoadingListener expectLoadPreloaded(LibCheck libCheck) { return this.addCheck( - new Check("onLoad:preloaded %s", expectedLibName) { + new Check("onLoad:preloaded %s", libCheck) { @Override public void onLoad( PlatformSpec platformSpec, @@ -126,44 +130,53 @@ public void onLoad( String libName, boolean isPreloaded, Path optionalLibPath) { - assertNull(optionalComponent); - assertEquals(libName, expectedLibName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); assertTrue(isPreloaded); } }); } - public TestLibraryLoadingListener expectLoad(String expectedComponent, String expectedLibName) { + public TestLibraryLoadingListener expectLoadFailure(String expectedLibName) { + return this.expectLoadFailure(new LibCheck(expectedLibName)); + } + + private TestLibraryLoadingListener expectLoadFailure(LibCheck libCheck) { return this.addCheck( - new Check("onLoad %s/%s", expectedComponent, expectedLibName) { + new Check("onLoadFailure %s", libCheck) { @Override - public void onLoad( + public void onLoadFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - boolean isPreloaded, - Path optionalLibPath) { - assertEquals(optionalComponent, expectedComponent); - assertEquals(libName, expectedLibName); + Throwable optionalCause) { + libCheck.assertMatches(platformSpec, optionalComponent, libName); } }); } public TestLibraryLoadingListener expectTempFileCreated(String expectedLibName) { + return this.expectTempFileCreated(new LibCheck(expectedLibName)); + } + + private TestLibraryLoadingListener expectTempFileCreated(LibCheck libCheck) { return this.addCheck( - new Check("onTempFileCreated %s", expectedLibName) { + new Check("onTempFileCreated %s", libCheck) { @Override public void onTempFileCreated( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - assertNull(optionalComponent); - assertEquals(expectedLibName, libName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); + assertNotNull(tempFile); } }); } public TestLibraryLoadingListener expectTempFileCreationFailure(String expectedLibName) { + return this.expectTempFileCreationFailure(new LibCheck(expectedLibName)); + } + + private TestLibraryLoadingListener expectTempFileCreationFailure(LibCheck libCheck) { return this.addCheck( - new Check("onTempFileCreationFailure %s", expectedLibName) { + new Check("onTempFileCreationFailure %s", libCheck) { @Override public void onTempFileCreationFailure( PlatformSpec platformSpec, @@ -173,20 +186,25 @@ public void onTempFileCreationFailure( String libExt, Path optionalTempFile, Throwable optionalCause) { - assertNull(optionalComponent); - assertEquals(expectedLibName, libName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); + assertNotNull(tempDir); + assertNotNull(libExt); } }); } public TestLibraryLoadingListener expectTempFileCleanup(String expectedLibName) { + return this.expectTempFileCleanup(new LibCheck(expectedLibName)); + } + + public TestLibraryLoadingListener expectTempFileCleanup(LibCheck libCheck) { return this.addCheck( - new Check("onTempFileCreationCleanup %s", expectedLibName) { + new Check("onTempFileCreationCleanup %s", libCheck) { @Override public void onTempFileCleanup( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - assertNull(optionalComponent); - assertEquals(expectedLibName, libName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); + assertNotNull(tempFile); } }); } @@ -228,7 +246,7 @@ public void onResolveDynamicFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) { + Throwable optionalCause) { this.nextCheck( check -> check.onResolveDynamicFailure(platformSpec, optionalComponent, libName, optionalCause)); @@ -251,7 +269,7 @@ public void onLoadFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) { + Throwable optionalCause) { this.nextCheck( check -> check.onLoadFailure(platformSpec, optionalComponent, libName, optionalCause)); } @@ -293,12 +311,12 @@ public void onTempFileCleanup( check -> check.onTempFileCleanup(platformSpec, optionalComponent, libName, tempFile)); } - TestLibraryLoadingListener addCheck(Check check) { + public TestLibraryLoadingListener addCheck(Check check) { this.checks.addLast(check); return this; } - void nextCheck(CheckInvocation invocation) { + private void nextCheck(CheckInvocation invocation) { Check nextCheck = this.checks.isEmpty() ? Check.NOTHING : this.checks.removeFirst(); try { invocation.invoke(nextCheck); @@ -309,58 +327,8 @@ void nextCheck(CheckInvocation invocation) { } } } - - static final class LibCheck { - private final PlatformSpec expectedPlatformSpec; - private final String expectedComponent; - private final String expectedLibName; - - LibCheck(PlatformSpec expectedPlatformSpec, String expectedLibName) { - this(null, null, expectedLibName); - } - - LibCheck(String expectedComponent, String expectedLibName) { - this(null, expectedComponent, expectedLibName); - } - - LibCheck(String expectedLibName) { - this(null, null, expectedLibName); - } - - LibCheck(PlatformSpec expectedPlatformSpec, String expectedComponent, String expectedLibName) { - this.expectedPlatformSpec = expectedPlatformSpec; - this.expectedComponent = expectedComponent; - this.expectedLibName = expectedLibName; - } - - void assertMatches(PlatformSpec platformSpec, String optionalComponent, String libName) { - if (this.expectedPlatformSpec != null) { - assertEquals(this.expectedPlatformSpec, platformSpec); - } - if (this.expectedComponent == null) { - assertNull(optionalComponent); - } else { - assertEquals(this.expectedComponent, optionalComponent); - } - assertEquals(this.expectedLibName, libName); - } - - @Override - public String toString() { - if (this.expectedComponent == null) { - return this.expectedLibName; - } else { - return this.expectedComponent + "/" + this.expectedLibName; - } - } - } - - @FunctionalInterface - interface CheckInvocation { - void invoke(Check check); - } - - abstract static class Check implements LibraryLoadingListener { + + public abstract static class Check implements LibraryLoadingListener { static final Check NOTHING = new Check("nothing") {}; private final String name; @@ -389,7 +357,7 @@ public void onLoadFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) { + Throwable optionalCause) { this.fallback("onLoadFailure", platformSpec, optionalComponent, libName, optionalCause); } @@ -409,7 +377,7 @@ public void onResolveDynamicFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) { + Throwable optionalCause) { this.fallback( "onResolveDynamicFailure", platformSpec, optionalComponent, libName, optionalCause); } @@ -467,4 +435,61 @@ public String toString() { return this.name; } } + + @FunctionalInterface + interface CheckInvocation { + void invoke(Check check); + } + + + public static final class LibCheck { + private final PlatformSpec expectedPlatformSpec; + private final String expectedComponent; + private final String expectedLibName; + + LibCheck(PlatformSpec expectedPlatformSpec, String expectedLibName) { + this(null, null, expectedLibName); + } + + LibCheck(String expectedComponent, String expectedLibName) { + this(null, expectedComponent, expectedLibName); + } + + LibCheck(String expectedLibName) { + this(null, null, expectedLibName); + } + + LibCheck(PlatformSpec expectedPlatformSpec, String expectedComponent, String expectedLibName) { + this.expectedPlatformSpec = expectedPlatformSpec; + this.expectedComponent = expectedComponent; + this.expectedLibName = expectedLibName; + } + + void assertMatches(PlatformSpec platformSpec, String optionalComponent, String libName) { + if (this.expectedPlatformSpec == null) { + // if no expectedPlatformSpec was provided -- just check that platformSpec is not null + assertNotNull(platformSpec); + } else { + assertEquals(this.expectedPlatformSpec, platformSpec); + } + + if (this.expectedComponent == null) { + // a null expectedComponent is treated as not expecting a component + assertNull(optionalComponent); + } else { + assertEquals(this.expectedComponent, optionalComponent); + } + + assertEquals(this.expectedLibName, libName); + } + + @Override + public String toString() { + if (this.expectedComponent == null) { + return this.expectedLibName; + } else { + return this.expectedComponent + "/" + this.expectedLibName; + } + } + } } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java index c9f73b0580b..bcded824877 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java @@ -19,7 +19,7 @@ public final void onLoadFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) { + Throwable optionalCause) { this.throwException("loadFailure"); } @@ -38,7 +38,7 @@ public final void onResolveDynamicFailure( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadException optionalCause) { + Throwable optionalCause) { this.throwException("resolveDynamicFailure"); } From 937601644a88fdcf877cbd5e5f891d16265922dc Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 10:13:20 -0500 Subject: [PATCH 15/27] More JavaDoc & spotless --- .../nativeloader/LibraryLoadingListener.java | 20 +++++--- .../TestLibraryLoadingListener.java | 48 +++++++++---------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index dc5667afcd6..f41453fba50 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -5,10 +5,10 @@ public interface LibraryLoadingListener { /** - * Called when a dynamic library is "resolved" - * This includes an attempt to resolve a pre-loaded library - * - * If the library is pre-loaded optionalUrl will be null + * Called when a dynamic library is resolved This includes resolving a pre-loaded or already + * loaded library + * + *

If the library is pre-loaded optionalUrl will be null */ default void onResolveDynamic( PlatformSpec platformSpec, @@ -18,8 +18,8 @@ default void onResolveDynamic( URL optionalUrl) {} /** - * Called when a dynamic library fails to "resolve" - * This can occur because the library was not found -- or an exception occurred during resolution + * Called when a dynamic library fails to resolve This can occur because the library was not found + * -- or an exception occurred during resolution */ default void onResolveDynamicFailure( PlatformSpec platformSpec, @@ -27,6 +27,10 @@ default void onResolveDynamicFailure( String libName, Throwable optionalCause) {} + /** + * Called when a dynamic library loads successfully This includes loading a pre-loaded or already + * loaded library + */ default void onLoad( PlatformSpec platformSpec, String optionalComponent, @@ -34,15 +38,18 @@ default void onLoad( boolean isPreloaded, Path optionalLibPath) {} + /** Called when a dynamic library fails to load */ default void onLoadFailure( PlatformSpec platformSpec, String optionalComponent, String libName, Throwable optionalCause) {} + /** Called when a temp file is successfully created to hold the library */ default void onTempFileCreated( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) {} + /** Called when a temp file could not be created */ default void onTempFileCreationFailure( PlatformSpec platformSpec, String optionalComponent, @@ -52,6 +59,7 @@ default void onTempFileCreationFailure( Path optionalTempFile, Throwable optionalCause) {} + /** Called when a temp is cleaned up */ default void onTempFileCleanup( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) {} } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index e54a765b9f0..bd0b78f80a0 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -55,9 +55,9 @@ public void onResolveDynamic( } public TestLibraryLoadingListener expectResolvePreloaded(String expectedLibName) { - return this.expectResolvePreloaded(new LibCheck(expectedLibName)); + return this.expectResolvePreloaded(new LibCheck(expectedLibName)); } - + private TestLibraryLoadingListener expectResolvePreloaded(LibCheck libCheck) { return this.addCheck( new Check("onResolveDynamic:preloaded %s", libCheck) { @@ -75,9 +75,9 @@ public void onResolveDynamic( } public TestLibraryLoadingListener expectResolveDynamicFailure(String expectedLibName) { - return this.expectResolveDynamicFailure(new LibCheck(expectedLibName)); + return this.expectResolveDynamicFailure(new LibCheck(expectedLibName)); } - + private TestLibraryLoadingListener expectResolveDynamicFailure(LibCheck libCheck) { return this.addCheck( new Check("onResolveDynamicFailure %s", libCheck) { @@ -93,13 +93,13 @@ public void onResolveDynamicFailure( } public TestLibraryLoadingListener expectLoad(String expectedLibName) { - return this.expectLoad(new LibCheck(expectedLibName)); + return this.expectLoad(new LibCheck(expectedLibName)); } public TestLibraryLoadingListener expectLoad(String expectedComponent, String expectedLibName) { return this.expectLoad(new LibCheck(expectedComponent, expectedLibName)); } - + private TestLibraryLoadingListener expectLoad(LibCheck libCheck) { return this.addCheck( new Check("onLoad %s", libCheck) { @@ -110,16 +110,15 @@ public void onLoad( String libName, boolean isPreloaded, Path optionalLibPath) { - libCheck.assertMatches(platformSpec, optionalComponent, libName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); } }); } - public TestLibraryLoadingListener expectLoadPreloaded(String expectedLibName) { - return this.expectLoadPreloaded(new LibCheck(expectedLibName)); + return this.expectLoadPreloaded(new LibCheck(expectedLibName)); } - + private TestLibraryLoadingListener expectLoadPreloaded(LibCheck libCheck) { return this.addCheck( new Check("onLoad:preloaded %s", libCheck) { @@ -137,9 +136,9 @@ public void onLoad( } public TestLibraryLoadingListener expectLoadFailure(String expectedLibName) { - return this.expectLoadFailure(new LibCheck(expectedLibName)); + return this.expectLoadFailure(new LibCheck(expectedLibName)); } - + private TestLibraryLoadingListener expectLoadFailure(LibCheck libCheck) { return this.addCheck( new Check("onLoadFailure %s", libCheck) { @@ -149,15 +148,15 @@ public void onLoadFailure( String optionalComponent, String libName, Throwable optionalCause) { - libCheck.assertMatches(platformSpec, optionalComponent, libName); + libCheck.assertMatches(platformSpec, optionalComponent, libName); } }); } public TestLibraryLoadingListener expectTempFileCreated(String expectedLibName) { - return this.expectTempFileCreated(new LibCheck(expectedLibName)); + return this.expectTempFileCreated(new LibCheck(expectedLibName)); } - + private TestLibraryLoadingListener expectTempFileCreated(LibCheck libCheck) { return this.addCheck( new Check("onTempFileCreated %s", libCheck) { @@ -171,9 +170,9 @@ public void onTempFileCreated( } public TestLibraryLoadingListener expectTempFileCreationFailure(String expectedLibName) { - return this.expectTempFileCreationFailure(new LibCheck(expectedLibName)); + return this.expectTempFileCreationFailure(new LibCheck(expectedLibName)); } - + private TestLibraryLoadingListener expectTempFileCreationFailure(LibCheck libCheck) { return this.addCheck( new Check("onTempFileCreationFailure %s", libCheck) { @@ -194,9 +193,9 @@ public void onTempFileCreationFailure( } public TestLibraryLoadingListener expectTempFileCleanup(String expectedLibName) { - return this.expectTempFileCleanup(new LibCheck(expectedLibName)); + return this.expectTempFileCleanup(new LibCheck(expectedLibName)); } - + public TestLibraryLoadingListener expectTempFileCleanup(LibCheck libCheck) { return this.addCheck( new Check("onTempFileCreationCleanup %s", libCheck) { @@ -327,7 +326,7 @@ private void nextCheck(CheckInvocation invocation) { } } } - + public abstract static class Check implements LibraryLoadingListener { static final Check NOTHING = new Check("nothing") {}; @@ -441,7 +440,6 @@ interface CheckInvocation { void invoke(Check check); } - public static final class LibCheck { private final PlatformSpec expectedPlatformSpec; private final String expectedComponent; @@ -467,19 +465,19 @@ public static final class LibCheck { void assertMatches(PlatformSpec platformSpec, String optionalComponent, String libName) { if (this.expectedPlatformSpec == null) { - // if no expectedPlatformSpec was provided -- just check that platformSpec is not null + // if no expectedPlatformSpec was provided -- just check that platformSpec is not null assertNotNull(platformSpec); } else { assertEquals(this.expectedPlatformSpec, platformSpec); } - + if (this.expectedComponent == null) { - // a null expectedComponent is treated as not expecting a component + // a null expectedComponent is treated as not expecting a component assertNull(optionalComponent); } else { assertEquals(this.expectedComponent, optionalComponent); } - + assertEquals(this.expectedLibName, libName); } From 20c3e0277fc126aefae2d73e62586e502467c508 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 10:52:28 -0500 Subject: [PATCH 16/27] More end-to-end listener tests - adding temp file creation failure test --- .../nativeloader/LibraryLoadingListener.java | 2 +- .../datadog/nativeloader/NativeLoader.java | 59 +++++++++---------- .../nativeloader/NativeLoaderTest.java | 8 ++- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index f41453fba50..f998ed6bc03 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -59,7 +59,7 @@ default void onTempFileCreationFailure( Path optionalTempFile, Throwable optionalCause) {} - /** Called when a temp is cleaned up */ + /** Called when a temp file is cleaned up */ default void onTempFileCleanup( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) {} } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 1eae3792d8d..5a4a84ed1aa 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -331,44 +331,43 @@ private LibFile resolveDynamicImpl( throw ex; } + // For listener purposes - at this point resolution completed successfully + // Although, the resolveDynamic method can still fail if we need a temp file and cannot create it allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, url); - return toLibFile(this.tempDir, platformSpec, optionalComponent, libName, url, allListeners); + + if (url.getProtocol().equals("file")) { + return LibFile.fromFile( + platformSpec, optionalComponent, libName, new File(url.getPath()), allListeners); + } else { + Path tempFile; + try { + tempFile = createTempFile(this.tempDir, platformSpec, libName, url); + allListeners.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); + + return LibFile.fromTempFile(platformSpec, optionalComponent, libName, tempFile.toFile(), allListeners); + } catch ( Throwable t ) { + allListeners.onTempFileCreationFailure(platformSpec, optionalComponent, libName, this.tempDir, libName, null, t); + + throw new LibraryLoadException(libName, t); + } + } } - private static LibFile toLibFile( + private static Path createTempFile( Path tempDir, PlatformSpec platformSpec, - String optionalComponent, String libName, - URL url, - SafeLibraryLoadingListener allListeners) - throws LibraryLoadException { - if (url.getProtocol().equals("file")) { - return LibFile.fromFile( - platformSpec, optionalComponent, libName, new File(url.getPath()), allListeners); - } else { - String libExt = PathUtils.dynamicLibExtension(platformSpec); - - Path tempFile = null; - try { - tempFile = TempFileHelper.createTempFile(tempDir, libName, libExt); - - try (InputStream in = url.openStream()) { - Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING); - } - - allListeners.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); + URL url) + throws IOException + { + String libExt = PathUtils.dynamicLibExtension(platformSpec); - return LibFile.fromTempFile( - platformSpec, optionalComponent, libName, tempFile.toFile(), allListeners); - - } catch (Throwable t) { - allListeners.onTempFileCreationFailure( - platformSpec, optionalComponent, libName, tempDir, libExt, tempFile, t); - - throw new LibraryLoadException(libName, t); - } + Path tempFile = TempFileHelper.createTempFile(tempDir, libName, libExt); + try (InputStream in = url.openStream()) { + Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING); } + + return tempFile; } static boolean delete(File tempFile) { diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 3af170ce540..5a9569e0e40 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -376,9 +376,15 @@ public void fromJarBackedClassLoader_with_unwritable_tempDir() try (URLClassLoader classLoader = createClassLoader(jar)) { NativeLoader loader = NativeLoader.builder().fromClassLoader(classLoader).tempDir(noWriteDir).build(); + + TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). + expectResolveDynamic("dummy"). + expectTempFileCreationFailure("dummy"); // unable to resolve to a File because tempDir isn't writable - assertThrows(LibraryLoadException.class, () -> loader.resolveDynamic("dummy")); + assertThrows(LibraryLoadException.class, () -> loader.resolveDynamic("dummy", scopedListener)); + + scopedListener.assertDone(); } finally { deleteHelper(noWriteDir); } From f6381ed453c3b24314d6f85565be8016c7bee9e5 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 11:03:20 -0500 Subject: [PATCH 17/27] More end-to-end listener testing - checking temp file creation failure --- .../nativeloader/LibraryLoadingListener.java | 4 +-- .../datadog/nativeloader/NativeLoader.java | 34 +++++++++---------- .../nativeloader/NativeLoaderTest.java | 14 ++++---- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index f998ed6bc03..a2d7e04e6b8 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -5,7 +5,7 @@ public interface LibraryLoadingListener { /** - * Called when a dynamic library is resolved This includes resolving a pre-loaded or already + * Called when a dynamic library is resolved. This includes resolving a pre-loaded or already * loaded library * *

If the library is pre-loaded optionalUrl will be null @@ -18,7 +18,7 @@ default void onResolveDynamic( URL optionalUrl) {} /** - * Called when a dynamic library fails to resolve This can occur because the library was not found + * Called when a dynamic library fails to resolve. This can occur because the library was not found * -- or an exception occurred during resolution */ default void onResolveDynamicFailure( diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 5a4a84ed1aa..af9ef85360f 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -332,41 +332,39 @@ private LibFile resolveDynamicImpl( } // For listener purposes - at this point resolution completed successfully - // Although, the resolveDynamic method can still fail if we need a temp file and cannot create it + // Although, the resolveDynamic method can still fail if we need a temp file and cannot create + // it allListeners.onResolveDynamic(platformSpec, optionalComponent, libName, isPreloaded, url); - - if (url.getProtocol().equals("file")) { + + if (url.getProtocol().equals("file")) { return LibFile.fromFile( - platformSpec, optionalComponent, libName, new File(url.getPath()), allListeners); + platformSpec, optionalComponent, libName, new File(url.getPath()), allListeners); } else { Path tempFile; try { - tempFile = createTempFile(this.tempDir, platformSpec, libName, url); - allListeners.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); - - return LibFile.fromTempFile(platformSpec, optionalComponent, libName, tempFile.toFile(), allListeners); - } catch ( Throwable t ) { - allListeners.onTempFileCreationFailure(platformSpec, optionalComponent, libName, this.tempDir, libName, null, t); - + tempFile = createTempFile(this.tempDir, platformSpec, libName, url); + allListeners.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile); + + return LibFile.fromTempFile( + platformSpec, optionalComponent, libName, tempFile.toFile(), allListeners); + } catch (Throwable t) { + allListeners.onTempFileCreationFailure( + platformSpec, optionalComponent, libName, this.tempDir, libName, null, t); + throw new LibraryLoadException(libName, t); } } } private static Path createTempFile( - Path tempDir, - PlatformSpec platformSpec, - String libName, - URL url) - throws IOException - { + Path tempDir, PlatformSpec platformSpec, String libName, URL url) throws IOException { String libExt = PathUtils.dynamicLibExtension(platformSpec); Path tempFile = TempFileHelper.createTempFile(tempDir, libName, libExt); try (InputStream in = url.openStream()) { Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING); } - + return tempFile; } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 5a9569e0e40..f99cb48047e 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -376,14 +376,16 @@ public void fromJarBackedClassLoader_with_unwritable_tempDir() try (URLClassLoader classLoader = createClassLoader(jar)) { NativeLoader loader = NativeLoader.builder().fromClassLoader(classLoader).tempDir(noWriteDir).build(); - - TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). - expectResolveDynamic("dummy"). - expectTempFileCreationFailure("dummy"); + + TestLibraryLoadingListener scopedListener = + new TestLibraryLoadingListener() + .expectResolveDynamic("dummy") + .expectTempFileCreationFailure("dummy"); // unable to resolve to a File because tempDir isn't writable - assertThrows(LibraryLoadException.class, () -> loader.resolveDynamic("dummy", scopedListener)); - + assertThrows( + LibraryLoadException.class, () -> loader.resolveDynamic("dummy", scopedListener)); + scopedListener.assertDone(); } finally { deleteHelper(noWriteDir); From e8d44db460da96b961dc9a142e262ef8dfd44ca7 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 11:30:38 -0500 Subject: [PATCH 18/27] spotless --- .../java/datadog/nativeloader/LibraryLoadingListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index a2d7e04e6b8..c2c9f003f53 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -18,8 +18,8 @@ default void onResolveDynamic( URL optionalUrl) {} /** - * Called when a dynamic library fails to resolve. This can occur because the library was not found - * -- or an exception occurred during resolution + * Called when a dynamic library fails to resolve. This can occur because the library was not + * found -- or an exception occurred during resolution */ default void onResolveDynamicFailure( PlatformSpec platformSpec, From 3bd056755aea657ab2450e57aa3ba20f9375e8c9 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 14:41:34 -0500 Subject: [PATCH 19/27] Adding coverage - exposed oversight in CompositeLibraryLoadingListener not implementing onLoadFailure - added loading failure tests - exposed missing / incomplete load variants in NativeLoader --- .../CompositeLibraryLoadingListener.java | 14 ++++++ .../java/datadog/nativeloader/LibFile.java | 19 ++++---- .../datadog/nativeloader/NativeLoader.java | 9 +++- .../CompositeLibraryLoadingListenerTest.java | 23 ++++++++++ .../nativeloader/NativeLoaderTest.java | 45 +++++++++++++++++++ .../TestLibraryLoadingListener.java | 19 ++++++-- 6 files changed, 116 insertions(+), 13 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index a22f5bf9add..70e7ca62786 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -71,6 +71,20 @@ public void onLoad( } } + @Override + public void onLoadFailure( + PlatformSpec platformSpec, + String optionalComponent, + String libName, + Throwable optionalCause) { + for (LibraryLoadingListener listener : this.listeners) { + try { + listener.onLoadFailure(platformSpec, optionalComponent, libName, optionalCause); + } catch (Throwable ignored) { + } + } + } + @Override public void onTempFileCreated( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java b/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java index 352abae8e76..80a1957e5e8 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibFile.java @@ -86,17 +86,18 @@ public void load() throws LibraryLoadException { try { Runtime.getRuntime().load(this.getAbsolutePath()); - this.listeners.onLoad( - this.platformSpec, - this.optionalComponent, - this.libName, - isPreloaded, - this.optionalFile.toPath()); + if (true) throw new RuntimeException("real load - worked?"); } catch (Throwable t) { - LibraryLoadException ex = new LibraryLoadException(this.libName, t); - this.listeners.onLoadFailure(this.platformSpec, this.optionalComponent, this.libName, ex); - throw ex; + this.listeners.onLoadFailure(this.platformSpec, this.optionalComponent, this.libName, t); + throw new LibraryLoadException(this.libName, t); } + + this.listeners.onLoad( + this.platformSpec, + this.optionalComponent, + this.libName, + isPreloaded, + this.optionalFile.toPath()); } /** Provides a File to the library -- returns null for pre-loaded libraries */ diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index af9ef85360f..0b2909f64ff 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -232,7 +232,14 @@ public void load(String libName, LibraryLoadingListener... scopedListeners) } /** Loads a library associated with an associated component */ - public void load(String component, String libName) throws LibraryLoadException {} + public void load(String component, String libName) throws LibraryLoadException { + this.loadImpl(component, libName, EMPTY_LISTENERS); + } + + public void load(String component, String libName, LibraryLoadingListener... scopedListeners) + throws LibraryLoadException { + this.loadImpl(component, libName, scopedListeners); + } private void loadImpl(String component, String libName, LibraryLoadingListener... scopedListeners) throws LibraryLoadException { diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java index 1a40f6a100f..0f1874aa7f3 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java @@ -1,6 +1,7 @@ package datadog.nativeloader; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.IOException; import java.net.MalformedURLException; @@ -56,6 +57,20 @@ public void onLoad() { listener2.assertDone(); } + @Test + public void onLoadFailure() { + TestLibraryLoadingListener listener1 = + new TestLibraryLoadingListener().expectLoadFailure("foo"); + + TestLibraryLoadingListener listener2 = listener1.copy(); + + listeners(listener1, listener2) + .onLoadFailure(PlatformSpec.defaultPlatformSpec(), null, "foo", null); + + listener1.assertDone(); + listener2.assertDone(); + } + @Test public void onTempFileCreated() { TestLibraryLoadingListener listener1 = @@ -134,10 +149,17 @@ public void join() { listener4.assertDone(); } + @Test + public void _toString() { + // just for coverage + assertNotNull(new CompositeLibraryLoadingListener().toString()); + } + /* * Constructs a composite listener that includes the provided listeners * To test robustness... * - adds in additional failing listeners + * - adds in additional default listeners * - shuffles the order of the listeners */ static CompositeLibraryLoadingListener listeners(LibraryLoadingListener... listeners) { @@ -145,6 +167,7 @@ static CompositeLibraryLoadingListener listeners(LibraryLoadingListener... liste shuffledListeners.addAll(Arrays.asList(listeners)); for (int i = 0; i < listeners.length; ++i) { + shuffledListeners.add(new LibraryLoadingListener() {}); shuffledListeners.add(new ThrowingLibraryLoadingListener()); } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index f99cb48047e..7f70e14ecdf 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -253,6 +253,23 @@ public void fromDir_with_component() throws LibraryLoadException { sharedListener.assertDone(); } + @Test + public void fromDir_load_with_component() { + NativeLoader loader = NativeLoader.builder().fromDir("test-data").build(); + + // lib file is a dummy, so fails during loading and linking + assertThrows(LibraryLoadException.class, () -> loader.load("comp1", "dummy")); + + TestLibraryLoadingListener scopedListener2 = + new TestLibraryLoadingListener() + .expectResolveDynamic("comp2", "dummy") + .expectLoadFailure("comp2", "dummy"); + + assertThrows(LibraryLoadException.class, () -> loader.load("comp2", "dummy", scopedListener2)); + + scopedListener2.assertDone(); + } + @Test public void fromDirBackedClassLoader() throws IOException, LibraryLoadException { // ClassLoader pulling from a directory, so there's still a normal file @@ -342,6 +359,34 @@ public void fromJarBackedClassLoader() throws IOException, LibraryLoadException } } + @Test + public void fromJarBackedClassLoader_load_with_component() + throws IOException, LibraryLoadException { + Path jar = jar("test-data"); + try { + try (URLClassLoader classLoader = createClassLoader(jar)) { + NativeLoader loader = NativeLoader.builder().fromClassLoader(classLoader).build(); + + // lib file is a dummy, so fails during loading and linking + assertThrows(LibraryLoadException.class, () -> loader.load("comp1", "dummy")); + + TestLibraryLoadingListener scopedListener2 = + new TestLibraryLoadingListener() + .expectResolveDynamic("comp2", "dummy") + .expectTempFileCreated("comp2", "dummy") + .expectLoadFailure("comp2", "dummy") + .expectTempFileCleanup("comp2", "dummy"); + + assertThrows( + LibraryLoadException.class, () -> loader.load("comp2", "dummy", scopedListener2)); + + scopedListener2.assertDone(); + } + } finally { + deleteHelper(jar); + } + } + @Test public void fromJarBackedClassLoader_with_tempDir() throws IOException, LibraryLoadException { Path jar = jar("test-data"); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index bd0b78f80a0..5822ed45fb0 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -139,6 +139,11 @@ public TestLibraryLoadingListener expectLoadFailure(String expectedLibName) { return this.expectLoadFailure(new LibCheck(expectedLibName)); } + public TestLibraryLoadingListener expectLoadFailure( + String expectedComponent, String expectedLibName) { + return this.expectLoadFailure(new LibCheck(expectedComponent, expectedLibName)); + } + private TestLibraryLoadingListener expectLoadFailure(LibCheck libCheck) { return this.addCheck( new Check("onLoadFailure %s", libCheck) { @@ -157,6 +162,11 @@ public TestLibraryLoadingListener expectTempFileCreated(String expectedLibName) return this.expectTempFileCreated(new LibCheck(expectedLibName)); } + public TestLibraryLoadingListener expectTempFileCreated( + String expectedComponent, String expectedLibName) { + return this.expectTempFileCreated(new LibCheck(expectedComponent, expectedLibName)); + } + private TestLibraryLoadingListener expectTempFileCreated(LibCheck libCheck) { return this.addCheck( new Check("onTempFileCreated %s", libCheck) { @@ -196,7 +206,12 @@ public TestLibraryLoadingListener expectTempFileCleanup(String expectedLibName) return this.expectTempFileCleanup(new LibCheck(expectedLibName)); } - public TestLibraryLoadingListener expectTempFileCleanup(LibCheck libCheck) { + public TestLibraryLoadingListener expectTempFileCleanup( + String expectedComponent, String expectedLibName) { + return this.expectTempFileCleanup(new LibCheck(expectedComponent, expectedLibName)); + } + + TestLibraryLoadingListener expectTempFileCleanup(LibCheck libCheck) { return this.addCheck( new Check("onTempFileCreationCleanup %s", libCheck) { @Override @@ -276,8 +291,6 @@ public void onLoadFailure( @Override public void onTempFileCreated( PlatformSpec platformSpec, String optionalComponent, String libName, Path tempFile) { - if (true) new RuntimeException("onTempFileCreated!"); - this.nextCheck( check -> check.onTempFileCreated(platformSpec, optionalComponent, libName, tempFile)); } From d77936d89b6f78e90ed93234db9868fd9f8b4a34 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 15:10:36 -0500 Subject: [PATCH 20/27] API clean-up Made order of PlatformSpec, component, libName consistent Changed component -> optionalComponent for clarity --- .../datadog/nativeloader/FlatDirLibraryResolver.java | 12 ++++++------ .../java/datadog/nativeloader/LibraryResolver.java | 2 +- .../main/java/datadog/nativeloader/NativeLoader.java | 2 +- .../nativeloader/NestedDirLibraryResolver.java | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/FlatDirLibraryResolver.java b/components/native-loader/src/main/java/datadog/nativeloader/FlatDirLibraryResolver.java index 29b8f5e16c5..9730b8de7f5 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/FlatDirLibraryResolver.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/FlatDirLibraryResolver.java @@ -13,7 +13,7 @@ private FlatDirLibraryResolver() {} @Override public final URL resolve( - PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName) + PathLocator pathLocator, PlatformSpec platformSpec, String optionalComponent, String libName) throws Exception { PathLocatorHelper pathLocatorHelper = new PathLocatorHelper(libName, pathLocator); @@ -28,22 +28,22 @@ public final URL resolve( if (libcPath != null) { String specializedPath = regularPath + "-" + libcPath; - url = pathLocatorHelper.locate(component, specializedPath + "/" + libFileName); + url = pathLocatorHelper.locate(optionalComponent, specializedPath + "/" + libFileName); if (url != null) return url; } - url = pathLocatorHelper.locate(component, regularPath + "/" + libFileName); + url = pathLocatorHelper.locate(optionalComponent, regularPath + "/" + libFileName); if (url != null) return url; - url = pathLocatorHelper.locate(component, osPath + "/" + libFileName); + url = pathLocatorHelper.locate(optionalComponent, osPath + "/" + libFileName); if (url != null) return url; // fallback to searching at top-level, mostly concession to good out-of-box behavior // with java.library.path - url = pathLocatorHelper.locate(component, libFileName); + url = pathLocatorHelper.locate(optionalComponent, libFileName); if (url != null) return url; - if (component != null) { + if (optionalComponent != null) { url = pathLocatorHelper.locate(null, libFileName); if (url != null) return url; } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolver.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolver.java index 4d28aae2794..40fd0fde3f8 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolver.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolver.java @@ -15,6 +15,6 @@ default boolean isPreloaded(PlatformSpec platform, String libName) { return false; } - URL resolve(PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName) + URL resolve(PathLocator pathLocator, PlatformSpec platformSpec, String optionalComponent, String libName) throws Exception; } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 0b2909f64ff..726665634c3 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -322,7 +322,7 @@ private LibFile resolveDynamicImpl( URL url; try { - url = this.libResolver.resolve(this.pathResolver, optionalComponent, platformSpec, libName); + url = this.libResolver.resolve(this.pathResolver, platformSpec, optionalComponent, libName); } catch (LibraryLoadException e) { // don't wrap if it is already a LibraryLoadException allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, e.getCause()); diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NestedDirLibraryResolver.java b/components/native-loader/src/main/java/datadog/nativeloader/NestedDirLibraryResolver.java index 966fd2c03e8..0137ca1d07e 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NestedDirLibraryResolver.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NestedDirLibraryResolver.java @@ -11,7 +11,7 @@ final class NestedDirLibraryResolver implements LibraryResolver { @Override public final URL resolve( - PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName) + PathLocator pathLocator, PlatformSpec platformSpec, String optionalComponent, String libName) throws Exception { PathLocatorHelper pathLocatorHelper = new PathLocatorHelper(libName, pathLocator); @@ -27,22 +27,22 @@ public final URL resolve( if (libcPath != null) { String specializedPath = regularPath + "/" + libcPath; - url = pathLocatorHelper.locate(component, specializedPath + "/" + libFileName); + url = pathLocatorHelper.locate(optionalComponent, specializedPath + "/" + libFileName); if (url != null) return url; } - url = pathLocatorHelper.locate(component, regularPath + "/" + libFileName); + url = pathLocatorHelper.locate(optionalComponent, regularPath + "/" + libFileName); if (url != null) return url; - url = pathLocatorHelper.locate(component, osPath + "/" + libFileName); + url = pathLocatorHelper.locate(optionalComponent, osPath + "/" + libFileName); if (url != null) return url; // fallback to searching at top-level, mostly concession to good out-of-box behavior // with java.library.path - url = pathLocatorHelper.locate(component, libFileName); + url = pathLocatorHelper.locate(optionalComponent, libFileName); if (url != null) return url; - if (component != null) { + if (optionalComponent != null) { url = pathLocatorHelper.locate(null, libFileName); if (url != null) return url; } From 9ae3be88ce0bf1c2bb7525a01081e67b50377add Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 15:19:07 -0500 Subject: [PATCH 21/27] API clean-up component -> optionalComponent --- .../ClassLoaderResourcePathLocator.java | 4 +-- .../nativeloader/LibDirBasedPathLocator.java | 4 +-- .../datadog/nativeloader/NativeLoader.java | 2 +- .../datadog/nativeloader/PathLocator.java | 2 +- .../nativeloader/PathLocatorHelper.java | 4 +-- .../nativeloader/CapturingPathLocator.java | 4 +-- .../nativeloader/NativeLoaderTest.java | 30 +++++++++++++++++++ 7 files changed, 40 insertions(+), 10 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/ClassLoaderResourcePathLocator.java b/components/native-loader/src/main/java/datadog/nativeloader/ClassLoaderResourcePathLocator.java index 4563facfb81..7ddca99bb3b 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/ClassLoaderResourcePathLocator.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/ClassLoaderResourcePathLocator.java @@ -14,8 +14,8 @@ public ClassLoaderResourcePathLocator(final ClassLoader classLoader, final Strin } @Override - public URL locate(String component, String path) { - return this.classLoader.getResource(PathUtils.concatPath(component, this.baseResource, path)); + public URL locate(String optionalComponent, String path) { + return this.classLoader.getResource(PathUtils.concatPath(optionalComponent, this.baseResource, path)); } @Override diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibDirBasedPathLocator.java b/components/native-loader/src/main/java/datadog/nativeloader/LibDirBasedPathLocator.java index 5199ae0fc3a..2ed0528c161 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibDirBasedPathLocator.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibDirBasedPathLocator.java @@ -14,8 +14,8 @@ public LibDirBasedPathLocator(File... libDirs) { } @Override - public URL locate(String component, String path) { - String fullPath = PathUtils.concatPath(component, path); + public URL locate(String optionalComponent, String path) { + String fullPath = PathUtils.concatPath(optionalComponent, path); for (File libDir : this.libDirs) { File libFile = new File(libDir, fullPath); diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 726665634c3..0f612b2ba0b 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -17,7 +17,7 @@ import java.util.Set; /** - * NativeLoader is intended as more feature rich replacement for calling {@link + * NativeLoader is intended as a more feature rich replacement for calling {@link * System#loadLibrary(String)} directly. NativeLoader can be used to find the corresponding platform * specific library using pluggable strategies -- for both path determination {@link * LibraryResolver} and path resolution {@link PathLocator} diff --git a/components/native-loader/src/main/java/datadog/nativeloader/PathLocator.java b/components/native-loader/src/main/java/datadog/nativeloader/PathLocator.java index e7cc2e14f20..aae3c2e0b5a 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/PathLocator.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/PathLocator.java @@ -14,5 +14,5 @@ public interface PathLocator { *

If the returned URL uses a non-file protocol, then {@link NativeLoader} will call {@link * URL#openStream()} and copy the contents to a temporary file */ - URL locate(String component, String path) throws Exception; + URL locate(String optionalComponent, String path) throws Exception; } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/PathLocatorHelper.java b/components/native-loader/src/main/java/datadog/nativeloader/PathLocatorHelper.java index 38c976c5c86..8842d642afb 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/PathLocatorHelper.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/PathLocatorHelper.java @@ -18,9 +18,9 @@ public PathLocatorHelper(String libName, PathLocator locator) { } @Override - public URL locate(String component, String path) { + public URL locate(String optionalComponent, String path) { try { - return this.locator.locate(component, path); + return this.locator.locate(optionalComponent, path); } catch (Throwable t) { if (this.firstCause == null) this.firstCause = t; return null; diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CapturingPathLocator.java b/components/native-loader/src/test/java/datadog/nativeloader/CapturingPathLocator.java index 257e5ba9958..1e198dcd4fb 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/CapturingPathLocator.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/CapturingPathLocator.java @@ -87,8 +87,8 @@ public CapturingPathLocator(int simulateNotFoundCount) { } @Override - public URL locate(String component, String path) { - this.locateRequests.addLast(new LocateRequest(component, path)); + public URL locate(String optionalComponent, String path) { + this.locateRequests.addLast(new LocateRequest(optionalComponent, path)); if (this.numRequests++ < this.simulateNotFoundCount) return null; try { diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 7f70e14ecdf..1c7594977db 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -158,6 +158,36 @@ public void loadFailure() throws LibraryLoadException { // & link assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); } + + @Test + public void resolutionFailure_in_LibraryResolver() { + NativeLoader loader = NativeLoader.builder().libResolver((pathLocator, platformSpec, component, libName) -> { + throw new Exception("boom!"); + }).build(); + + TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). + expectResolveDynamicFailure("dummy"); + + assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); + + scopedListener.assertDone(); + } + + @Test + public void resolutionFailure_in_PathLocator() { + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + + NativeLoader loader = NativeLoader.builder().addListener(sharedListener).pathLocator(() -> { + + }).build(); + + TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). + expectResolveDynamicFailure("dummy"); + + assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); + + scopedListener.assertDone(); + } @Test public void fromDir() throws LibraryLoadException { From 1f99359ad7b115b48ba954da17d77c3be325b3b4 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 15:25:48 -0500 Subject: [PATCH 22/27] Fixing up oversights in previous parameter reordering --- .../main/java/datadog/nativeloader/LibraryResolvers.java | 4 ++-- .../java/datadog/nativeloader/CapturingPathLocator.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolvers.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolvers.java index 6ef8cec3c4b..528ab545fd8 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolvers.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolvers.java @@ -27,9 +27,9 @@ public boolean isPreloaded(PlatformSpec platform, String libName) { @Override public URL resolve( - PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName) + PathLocator pathLocator, PlatformSpec platformSpec, String optionalComponent, String libName) throws Exception { - return baseResolver.resolve(pathLocator, component, platformSpec, libName); + return baseResolver.resolve(pathLocator, platformSpec, optionalComponent, libName); } }; } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CapturingPathLocator.java b/components/native-loader/src/test/java/datadog/nativeloader/CapturingPathLocator.java index 1e198dcd4fb..b002b2c144d 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/CapturingPathLocator.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/CapturingPathLocator.java @@ -32,7 +32,7 @@ public static final void test( String comp = "comp"; CapturingPathLocator fullCaptureLocator = new CapturingPathLocator(Integer.MAX_VALUE); - resolver.resolve(fullCaptureLocator, comp, platformSpec, "test"); + resolver.resolve(fullCaptureLocator, platformSpec, comp, "test"); for (int i = 0; !fullCaptureLocator.isEmpty(); ++i) { if (i >= expectedPaths.length) { @@ -52,7 +52,7 @@ public static final void test( for (int i = 0; i < expectedPaths.length; ++i) { CapturingPathLocator fallbackLocator = new CapturingPathLocator(i); - resolver.resolve(fallbackLocator, comp, platformSpec, "test"); + resolver.resolve(fallbackLocator, platformSpec, comp, "test"); for (int j = 0; j <= i; ++j) { fallbackLocator.assertRequested(comp, expectedPaths[j]); @@ -62,7 +62,7 @@ public static final void test( if (withSkipCompFallback) { CapturingPathLocator fallbackLocator = new CapturingPathLocator(expectedPaths.length); - resolver.resolve(fallbackLocator, comp, platformSpec, "test"); + resolver.resolve(fallbackLocator, platformSpec, comp, "test"); for (int j = 0; j < expectedPaths.length; ++j) { fallbackLocator.assertRequested(comp, expectedPaths[j]); From 244807f94864aa5a27707a7cbe21c40df71c43a3 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 15:26:27 -0500 Subject: [PATCH 23/27] More test coverage Adding coverage for exceptions in LibraryResolver and PathLocator --- .../java/datadog/nativeloader/NativeLoaderTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 1c7594977db..e4c377bb914 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -177,16 +177,15 @@ public void resolutionFailure_in_LibraryResolver() { public void resolutionFailure_in_PathLocator() { TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); - NativeLoader loader = NativeLoader.builder().addListener(sharedListener).pathLocator(() -> { - + NativeLoader loader = NativeLoader.builder().addListener(sharedListener).pathLocator((comp, path) -> { + throw new Exception("boom!"); }).build(); - TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). - expectResolveDynamicFailure("dummy"); + sharedListener.expectResolveDynamicFailure("dummy"); - assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); + assertThrows(LibraryLoadException.class, () -> loader.load("dummy")); - scopedListener.assertDone(); + sharedListener.assertDone(); } @Test From 1e458b2c62e2d6465b449fae7d7ef22945ebfaad Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 15:56:53 -0500 Subject: [PATCH 24/27] More coverage - checking that proper exceptions are passed to listener - covering NativeLoader.isPlatformSupported --- .../datadog/nativeloader/NativeLoader.java | 25 ++++++--------- .../CompositeLibraryLoadingListenerTest.java | 2 +- .../nativeloader/NativeLoaderTest.java | 16 +++++++--- .../TestLibraryLoadingListener.java | 32 ++++++++++++++++++- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 0f612b2ba0b..ca2c45c8c61 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -208,7 +208,9 @@ private NativeLoader(Builder builder) { } public boolean isPlatformSupported() { - return !this.defaultPlatformSpec.isUnknownOs() && !this.defaultPlatformSpec.isUnknownOs(); + if ( this.defaultPlatformSpec.isUnknownOs() ) return false; + if ( this.defaultPlatformSpec.isUnknownArch() ) return false; + return true; } /** Indicates if a library is considered "pre-loaded" */ @@ -253,7 +255,7 @@ private void loadImpl(String component, String libName, LibraryLoadingListener.. /** Resolves a library to a LibFile - creating a temporary file if necessary */ public LibFile resolveDynamic(String libName) throws LibraryLoadException { - return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName); + return this.resolveDynamicImpl(this.defaultPlatformSpec, null, libName, EMPTY_LISTENERS); } public LibFile resolveDynamic(String libName, LibraryLoadingListener... scopedListeners) @@ -263,7 +265,7 @@ public LibFile resolveDynamic(String libName, LibraryLoadingListener... scopedLi /** Resolves a library with an associated component */ public LibFile resolveDynamic(String component, String libName) throws LibraryLoadException { - return this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName); + return this.resolveDynamicImpl(this.defaultPlatformSpec, component, libName, EMPTY_LISTENERS); } /** @@ -272,7 +274,7 @@ public LibFile resolveDynamic(String component, String libName) throws LibraryLo */ public LibFile resolveDynamic(PlatformSpec platformSpec, String libName) throws LibraryLoadException { - return this.resolveDynamicImpl(platformSpec, null, libName); + return this.resolveDynamicImpl(platformSpec, null, libName, EMPTY_LISTENERS); } public LibFile resolveDynamic( @@ -287,20 +289,14 @@ public LibFile resolveDynamic( */ public LibFile resolveDynamic(PlatformSpec platformSpec, String component, String libName) throws LibraryLoadException { - return this.resolveDynamicImpl(platformSpec, component, libName); - } - - private LibFile resolveDynamicImpl( - PlatformSpec platformSpec, String optionalComponent, String libName) - throws LibraryLoadException { - return this.resolveDynamicImpl(platformSpec, optionalComponent, libName, EMPTY_LISTENERS); + return this.resolveDynamicImpl(platformSpec, component, libName, EMPTY_LISTENERS); } private LibFile resolveDynamicImpl( PlatformSpec platformSpec, String optionalComponent, String libName, - LibraryLoadingListener... scopedListeners) + LibraryLoadingListener[] scopedListeners) throws LibraryLoadException { SafeLibraryLoadingListener allListeners = (scopedListeners == null @@ -333,9 +329,8 @@ private LibFile resolveDynamicImpl( } if (url == null) { - LibraryLoadException ex = new LibraryLoadException(libName); - allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, ex); - throw ex; + allListeners.onResolveDynamicFailure(platformSpec, optionalComponent, libName, NO_CAUSE); + throw new LibraryLoadException(libName); } // For listener purposes - at this point resolution completed successfully diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java index 0f1874aa7f3..a14155f1135 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java @@ -38,7 +38,7 @@ public void onResolveDynamicFailure() { listeners(listener1, listener2) .onResolveDynamicFailure( - PlatformSpec.defaultPlatformSpec(), null, "foo", new LibraryLoadException("foo")); + PlatformSpec.defaultPlatformSpec(), null, "foo", new Exception("foo")); listener1.assertDone(); listener2.assertDone(); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index e4c377bb914..291cb73edd2 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -114,6 +114,8 @@ public void unsupportedPlatform() { PlatformSpec unsupportedOsSpec = TestPlatformSpec.of(UNSUPPORTED_OS, AARCH64); NativeLoader loader = NativeLoader.builder().platformSpec(unsupportedOsSpec).addListener(sharedListener).build(); + + assertFalse(loader.isPlatformSupported()); sharedListener.expectResolveDynamicFailure("dummy"); @@ -128,6 +130,8 @@ public void unsupportArch() { PlatformSpec unsupportedOsSpec = TestPlatformSpec.of(LINUX, UNSUPPORTED_ARCH); NativeLoader loader = NativeLoader.builder().platformSpec(unsupportedOsSpec).build(); + assertFalse(loader.isPlatformSupported()); + TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener().expectResolveDynamicFailure("dummy"); @@ -161,12 +165,14 @@ public void loadFailure() throws LibraryLoadException { @Test public void resolutionFailure_in_LibraryResolver() { + Exception exception = new Exception("boom!"); + NativeLoader loader = NativeLoader.builder().libResolver((pathLocator, platformSpec, component, libName) -> { - throw new Exception("boom!"); + throw exception; }).build(); TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). - expectResolveDynamicFailure("dummy"); + expectResolveDynamicFailure("dummy", exception); assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); @@ -177,11 +183,13 @@ public void resolutionFailure_in_LibraryResolver() { public void resolutionFailure_in_PathLocator() { TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + Exception exception = new Exception("boom!"); + NativeLoader loader = NativeLoader.builder().addListener(sharedListener).pathLocator((comp, path) -> { - throw new Exception("boom!"); + throw exception; }).build(); - sharedListener.expectResolveDynamicFailure("dummy"); + sharedListener.expectResolveDynamicFailure("dummy", exception); assertThrows(LibraryLoadException.class, () -> loader.load("dummy")); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index 5822ed45fb0..abb9c3528db 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -1,8 +1,10 @@ package datadog.nativeloader; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -16,6 +18,11 @@ public final class TestLibraryLoadingListener implements LibraryLoadingListener private Check failedCheck = null; private Throwable failedCause = null; + + // By design, listeners are supposed to receive the underlying cause not a LibraryLoadException directly + static final ThrowableCheck NOT_LIB_LOAD_EXCEPTION = (t) -> { + assertFalse(t instanceof LibraryLoadException, "LibraryLoadException - instead of underlying cause"); + }; public TestLibraryLoadingListener() { this.checks = new LinkedList<>(); @@ -77,8 +84,16 @@ public void onResolveDynamic( public TestLibraryLoadingListener expectResolveDynamicFailure(String expectedLibName) { return this.expectResolveDynamicFailure(new LibCheck(expectedLibName)); } + + public TestLibraryLoadingListener expectResolveDynamicFailure(String expectedLibName, Throwable expectedThrowable) { + return this.expectResolveDynamicFailure(new LibCheck(expectedLibName), (t) -> assertSame(expectedThrowable, t)); + } private TestLibraryLoadingListener expectResolveDynamicFailure(LibCheck libCheck) { + return this.expectResolveDynamicFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); + } + + private TestLibraryLoadingListener expectResolveDynamicFailure(LibCheck libCheck, ThrowableCheck throwableCheck) { return this.addCheck( new Check("onResolveDynamicFailure %s", libCheck) { @Override @@ -88,6 +103,7 @@ public void onResolveDynamicFailure( String libName, Throwable optionalCause) { libCheck.assertMatches(platformSpec, optionalComponent, libName); + throwableCheck.assertMatches(optionalCause); } }); } @@ -145,6 +161,10 @@ public TestLibraryLoadingListener expectLoadFailure( } private TestLibraryLoadingListener expectLoadFailure(LibCheck libCheck) { + return this.expectLoadFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); + } + + private TestLibraryLoadingListener expectLoadFailure(LibCheck libCheck, ThrowableCheck throwableCheck) { return this.addCheck( new Check("onLoadFailure %s", libCheck) { @Override @@ -154,6 +174,7 @@ public void onLoadFailure( String libName, Throwable optionalCause) { libCheck.assertMatches(platformSpec, optionalComponent, libName); + throwableCheck.assertMatches(optionalCause); } }); } @@ -184,6 +205,10 @@ public TestLibraryLoadingListener expectTempFileCreationFailure(String expectedL } private TestLibraryLoadingListener expectTempFileCreationFailure(LibCheck libCheck) { + return this.expectTempFileCreationFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); + } + + private TestLibraryLoadingListener expectTempFileCreationFailure(LibCheck libCheck, ThrowableCheck throwableCheck) { return this.addCheck( new Check("onTempFileCreationFailure %s", libCheck) { @Override @@ -453,7 +478,7 @@ interface CheckInvocation { void invoke(Check check); } - public static final class LibCheck { + static final class LibCheck { private final PlatformSpec expectedPlatformSpec; private final String expectedComponent; private final String expectedLibName; @@ -503,4 +528,9 @@ public String toString() { } } } + + @FunctionalInterface + interface ThrowableCheck { + void assertMatches(Throwable t); + } } From 517e3b49ef7e6a564e3e870d6a2fbd6ea12a1195 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 15:57:51 -0500 Subject: [PATCH 25/27] spotless --- .../ClassLoaderResourcePathLocator.java | 3 +- .../datadog/nativeloader/LibraryResolver.java | 3 +- .../nativeloader/LibraryResolvers.java | 5 +- .../datadog/nativeloader/NativeLoader.java | 6 +- .../nativeloader/NativeLoaderTest.java | 69 +++++++++++-------- .../TestLibraryLoadingListener.java | 47 ++++++++----- 6 files changed, 78 insertions(+), 55 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/ClassLoaderResourcePathLocator.java b/components/native-loader/src/main/java/datadog/nativeloader/ClassLoaderResourcePathLocator.java index 7ddca99bb3b..1aef0ff3ca8 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/ClassLoaderResourcePathLocator.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/ClassLoaderResourcePathLocator.java @@ -15,7 +15,8 @@ public ClassLoaderResourcePathLocator(final ClassLoader classLoader, final Strin @Override public URL locate(String optionalComponent, String path) { - return this.classLoader.getResource(PathUtils.concatPath(optionalComponent, this.baseResource, path)); + return this.classLoader.getResource( + PathUtils.concatPath(optionalComponent, this.baseResource, path)); } @Override diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolver.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolver.java index 40fd0fde3f8..66ab56c0169 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolver.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolver.java @@ -15,6 +15,7 @@ default boolean isPreloaded(PlatformSpec platform, String libName) { return false; } - URL resolve(PathLocator pathLocator, PlatformSpec platformSpec, String optionalComponent, String libName) + URL resolve( + PathLocator pathLocator, PlatformSpec platformSpec, String optionalComponent, String libName) throws Exception; } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolvers.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolvers.java index 528ab545fd8..bcbb434a3a1 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolvers.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryResolvers.java @@ -27,7 +27,10 @@ public boolean isPreloaded(PlatformSpec platform, String libName) { @Override public URL resolve( - PathLocator pathLocator, PlatformSpec platformSpec, String optionalComponent, String libName) + PathLocator pathLocator, + PlatformSpec platformSpec, + String optionalComponent, + String libName) throws Exception { return baseResolver.resolve(pathLocator, platformSpec, optionalComponent, libName); } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index ca2c45c8c61..ca954128991 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -208,9 +208,9 @@ private NativeLoader(Builder builder) { } public boolean isPlatformSupported() { - if ( this.defaultPlatformSpec.isUnknownOs() ) return false; - if ( this.defaultPlatformSpec.isUnknownArch() ) return false; - return true; + if (this.defaultPlatformSpec.isUnknownOs()) return false; + if (this.defaultPlatformSpec.isUnknownArch()) return false; + return true; } /** Indicates if a library is considered "pre-loaded" */ diff --git a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java index 291cb73edd2..0a44b91f912 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/NativeLoaderTest.java @@ -114,7 +114,7 @@ public void unsupportedPlatform() { PlatformSpec unsupportedOsSpec = TestPlatformSpec.of(UNSUPPORTED_OS, AARCH64); NativeLoader loader = NativeLoader.builder().platformSpec(unsupportedOsSpec).addListener(sharedListener).build(); - + assertFalse(loader.isPlatformSupported()); sharedListener.expectResolveDynamicFailure("dummy"); @@ -131,7 +131,7 @@ public void unsupportArch() { NativeLoader loader = NativeLoader.builder().platformSpec(unsupportedOsSpec).build(); assertFalse(loader.isPlatformSupported()); - + TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener().expectResolveDynamicFailure("dummy"); @@ -162,38 +162,47 @@ public void loadFailure() throws LibraryLoadException { // & link assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); } - + @Test public void resolutionFailure_in_LibraryResolver() { - Exception exception = new Exception("boom!"); - - NativeLoader loader = NativeLoader.builder().libResolver((pathLocator, platformSpec, component, libName) -> { - throw exception; - }).build(); - - TestLibraryLoadingListener scopedListener = new TestLibraryLoadingListener(). - expectResolveDynamicFailure("dummy", exception); - - assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); - - scopedListener.assertDone(); - } - + Exception exception = new Exception("boom!"); + + NativeLoader loader = + NativeLoader.builder() + .libResolver( + (pathLocator, platformSpec, component, libName) -> { + throw exception; + }) + .build(); + + TestLibraryLoadingListener scopedListener = + new TestLibraryLoadingListener().expectResolveDynamicFailure("dummy", exception); + + assertThrows(LibraryLoadException.class, () -> loader.load("dummy", scopedListener)); + + scopedListener.assertDone(); + } + @Test public void resolutionFailure_in_PathLocator() { - TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); - - Exception exception = new Exception("boom!"); - - NativeLoader loader = NativeLoader.builder().addListener(sharedListener).pathLocator((comp, path) -> { - throw exception; - }).build(); - - sharedListener.expectResolveDynamicFailure("dummy", exception); - - assertThrows(LibraryLoadException.class, () -> loader.load("dummy")); - - sharedListener.assertDone(); + TestLibraryLoadingListener sharedListener = new TestLibraryLoadingListener(); + + Exception exception = new Exception("boom!"); + + NativeLoader loader = + NativeLoader.builder() + .addListener(sharedListener) + .pathLocator( + (comp, path) -> { + throw exception; + }) + .build(); + + sharedListener.expectResolveDynamicFailure("dummy", exception); + + assertThrows(LibraryLoadException.class, () -> loader.load("dummy")); + + sharedListener.assertDone(); } @Test diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index abb9c3528db..7bce8b1245a 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -18,11 +18,15 @@ public final class TestLibraryLoadingListener implements LibraryLoadingListener private Check failedCheck = null; private Throwable failedCause = null; - - // By design, listeners are supposed to receive the underlying cause not a LibraryLoadException directly - static final ThrowableCheck NOT_LIB_LOAD_EXCEPTION = (t) -> { - assertFalse(t instanceof LibraryLoadException, "LibraryLoadException - instead of underlying cause"); - }; + + // By design, listeners are supposed to receive the underlying cause not a LibraryLoadException + // directly + static final ThrowableCheck NOT_LIB_LOAD_EXCEPTION = + (t) -> { + assertFalse( + t instanceof LibraryLoadException, + "LibraryLoadException - instead of underlying cause"); + }; public TestLibraryLoadingListener() { this.checks = new LinkedList<>(); @@ -84,16 +88,19 @@ public void onResolveDynamic( public TestLibraryLoadingListener expectResolveDynamicFailure(String expectedLibName) { return this.expectResolveDynamicFailure(new LibCheck(expectedLibName)); } - - public TestLibraryLoadingListener expectResolveDynamicFailure(String expectedLibName, Throwable expectedThrowable) { - return this.expectResolveDynamicFailure(new LibCheck(expectedLibName), (t) -> assertSame(expectedThrowable, t)); + + public TestLibraryLoadingListener expectResolveDynamicFailure( + String expectedLibName, Throwable expectedThrowable) { + return this.expectResolveDynamicFailure( + new LibCheck(expectedLibName), (t) -> assertSame(expectedThrowable, t)); } private TestLibraryLoadingListener expectResolveDynamicFailure(LibCheck libCheck) { - return this.expectResolveDynamicFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); + return this.expectResolveDynamicFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); } - - private TestLibraryLoadingListener expectResolveDynamicFailure(LibCheck libCheck, ThrowableCheck throwableCheck) { + + private TestLibraryLoadingListener expectResolveDynamicFailure( + LibCheck libCheck, ThrowableCheck throwableCheck) { return this.addCheck( new Check("onResolveDynamicFailure %s", libCheck) { @Override @@ -161,10 +168,11 @@ public TestLibraryLoadingListener expectLoadFailure( } private TestLibraryLoadingListener expectLoadFailure(LibCheck libCheck) { - return this.expectLoadFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); + return this.expectLoadFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); } - - private TestLibraryLoadingListener expectLoadFailure(LibCheck libCheck, ThrowableCheck throwableCheck) { + + private TestLibraryLoadingListener expectLoadFailure( + LibCheck libCheck, ThrowableCheck throwableCheck) { return this.addCheck( new Check("onLoadFailure %s", libCheck) { @Override @@ -205,10 +213,11 @@ public TestLibraryLoadingListener expectTempFileCreationFailure(String expectedL } private TestLibraryLoadingListener expectTempFileCreationFailure(LibCheck libCheck) { - return this.expectTempFileCreationFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); + return this.expectTempFileCreationFailure(libCheck, NOT_LIB_LOAD_EXCEPTION); } - - private TestLibraryLoadingListener expectTempFileCreationFailure(LibCheck libCheck, ThrowableCheck throwableCheck) { + + private TestLibraryLoadingListener expectTempFileCreationFailure( + LibCheck libCheck, ThrowableCheck throwableCheck) { return this.addCheck( new Check("onTempFileCreationFailure %s", libCheck) { @Override @@ -528,9 +537,9 @@ public String toString() { } } } - + @FunctionalInterface interface ThrowableCheck { - void assertMatches(Throwable t); + void assertMatches(Throwable t); } } From 1f98b7f457dbe48846913b5dade5eaff8c7d5fe9 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 16:07:14 -0500 Subject: [PATCH 26/27] More coverage - removed unused parameter in onTempFileCreationFailure - fixed oversight in Composite listener coverage - intended to use a shuffled set of listeners with errant & nop listeners injected - also provides full coverage of default methods on LibraryLoadingListener --- .../nativeloader/CompositeLibraryLoadingListener.java | 2 -- .../java/datadog/nativeloader/LibraryLoadingListener.java | 1 - .../src/main/java/datadog/nativeloader/NativeLoader.java | 4 ++-- .../nativeloader/CompositeLibraryLoadingListenerTest.java | 5 ++--- .../datadog/nativeloader/TestLibraryLoadingListener.java | 5 ----- .../datadog/nativeloader/ThrowingLibraryLoadingListener.java | 1 - 6 files changed, 4 insertions(+), 14 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index 70e7ca62786..a81c3700313 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -103,7 +103,6 @@ public void onTempFileCreationFailure( String libName, Path tempDir, String libExt, - Path optionalTempFile, Throwable optionalCause) { for (LibraryLoadingListener listener : this.listeners) { try { @@ -113,7 +112,6 @@ public void onTempFileCreationFailure( libName, tempDir, libExt, - optionalTempFile, optionalCause); } catch (Throwable ignored) { } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java index c2c9f003f53..8fa68a4feb0 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/LibraryLoadingListener.java @@ -56,7 +56,6 @@ default void onTempFileCreationFailure( String libName, Path tempDir, String libExt, - Path optionalTempFile, Throwable optionalCause) {} /** Called when a temp file is cleaned up */ diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index ca954128991..2329179e910 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -350,8 +350,8 @@ private LibFile resolveDynamicImpl( return LibFile.fromTempFile( platformSpec, optionalComponent, libName, tempFile.toFile(), allListeners); } catch (Throwable t) { - allListeners.onTempFileCreationFailure( - platformSpec, optionalComponent, libName, this.tempDir, libName, null, t); + allListeners.onTempFileCreationFailure( + platformSpec, optionalComponent, libName, this.tempDir, libName, t); throw new LibraryLoadException(libName, t); } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java index a14155f1135..019f09c2138 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/CompositeLibraryLoadingListenerTest.java @@ -100,7 +100,6 @@ public void onTempFileCreationFailure() { "foo", Paths.get("/tmp"), "dylib", - null, new IOException("perm")); listener1.assertDone(); @@ -163,7 +162,7 @@ public void _toString() { * - shuffles the order of the listeners */ static CompositeLibraryLoadingListener listeners(LibraryLoadingListener... listeners) { - List shuffledListeners = new ArrayList<>(listeners.length + 1); + List shuffledListeners = new ArrayList<>(listeners.length * 3); shuffledListeners.addAll(Arrays.asList(listeners)); for (int i = 0; i < listeners.length; ++i) { @@ -172,6 +171,6 @@ static CompositeLibraryLoadingListener listeners(LibraryLoadingListener... liste } Collections.shuffle(shuffledListeners); - return new CompositeLibraryLoadingListener(listeners); + return new CompositeLibraryLoadingListener(shuffledListeners); } } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index 7bce8b1245a..ff8ff413627 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -227,7 +227,6 @@ public void onTempFileCreationFailure( String libName, Path tempDir, String libExt, - Path optionalTempFile, Throwable optionalCause) { libCheck.assertMatches(platformSpec, optionalComponent, libName); assertNotNull(tempDir); @@ -336,7 +335,6 @@ public void onTempFileCreationFailure( String libName, Path tempDir, String libExt, - Path optionalTempFile, Throwable optionalCause) { this.nextCheck( check -> @@ -346,7 +344,6 @@ public void onTempFileCreationFailure( libName, tempDir, libExt, - optionalTempFile, optionalCause)); } @@ -441,7 +438,6 @@ public void onTempFileCreationFailure( String libName, Path tempDir, String libExt, - Path optionalTempFile, Throwable optionalCause) { this.fallback( "onTempFileCreationFailure", @@ -450,7 +446,6 @@ public void onTempFileCreationFailure( libName, tempDir, libExt, - optionalTempFile, optionalCause); } diff --git a/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java index bcded824877..75f0a2ed6fc 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/ThrowingLibraryLoadingListener.java @@ -55,7 +55,6 @@ public final void onTempFileCreationFailure( String libName, Path tempDir, String libExt, - Path optionalTempFile, Throwable optionalCause) { this.throwException("tempFileCreationFailure"); } From 5bdaa8826d03926008d7e60be3fae3f8eb7025ce Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 6 Nov 2025 16:07:48 -0500 Subject: [PATCH 27/27] spotless --- .../nativeloader/CompositeLibraryLoadingListener.java | 7 +------ .../src/main/java/datadog/nativeloader/NativeLoader.java | 2 +- .../datadog/nativeloader/TestLibraryLoadingListener.java | 7 +------ 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java index a81c3700313..6f87121b6d2 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/CompositeLibraryLoadingListener.java @@ -107,12 +107,7 @@ public void onTempFileCreationFailure( for (LibraryLoadingListener listener : this.listeners) { try { listener.onTempFileCreationFailure( - platformSpec, - optionalComponent, - libName, - tempDir, - libExt, - optionalCause); + platformSpec, optionalComponent, libName, tempDir, libExt, optionalCause); } catch (Throwable ignored) { } } diff --git a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java index 2329179e910..8a55eabe570 100644 --- a/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java +++ b/components/native-loader/src/main/java/datadog/nativeloader/NativeLoader.java @@ -350,7 +350,7 @@ private LibFile resolveDynamicImpl( return LibFile.fromTempFile( platformSpec, optionalComponent, libName, tempFile.toFile(), allListeners); } catch (Throwable t) { - allListeners.onTempFileCreationFailure( + allListeners.onTempFileCreationFailure( platformSpec, optionalComponent, libName, this.tempDir, libName, t); throw new LibraryLoadException(libName, t); diff --git a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java index ff8ff413627..9996de25a26 100644 --- a/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java +++ b/components/native-loader/src/test/java/datadog/nativeloader/TestLibraryLoadingListener.java @@ -339,12 +339,7 @@ public void onTempFileCreationFailure( this.nextCheck( check -> check.onTempFileCreationFailure( - platformSpec, - optionalComponent, - libName, - tempDir, - libExt, - optionalCause)); + platformSpec, optionalComponent, libName, tempDir, libExt, optionalCause)); } @Override