Skip to content

Commit

Permalink
#413: Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kaklakariada committed Jun 16, 2024
1 parent 65bb574 commit 7d49032
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.itsallcode.openfasttrace.core.serviceloader;

import java.net.URL;
import java.net.URLClassLoader;

/**
* This class loader will first try to load the class from the given URLs and
* then from the parent class loader, unlike {@link URLClassLoader} which does
* it the other way around.
* <p>
* This is based on
* https://medium.com/@isuru89/java-a-child-first-class-loader-cbd9c3d0305
* <p>
*/
class ChildFirstClassLoader extends URLClassLoader
{
ChildFirstClassLoader(final String name, final URL[] urls, final ClassLoader parent)
{
super(name, urls, parent);
}

@Override
protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException
{
// has the class loaded already?
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null)
{
try
{
// find the class from given jar urls
loadedClass = findClass(name);
}
catch (final ClassNotFoundException ignore)
{
// Hmmm... class does not exist in the given urls.
// Let's try finding it in our parent classloader.
// this'll throw ClassNotFoundException in failure.
loadedClass = super.loadClass(name, resolve);
}
}

if (resolve)
{ // marked to resolve
resolveClass(loadedClass);
}
return loadedClass;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,43 @@

import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import java.util.logging.Logger;
import java.util.stream.Stream;

final class ClassPathServiceLoader<T> implements Loader<T>
{
private static final Logger LOGGER = Logger.getLogger(ClassPathServiceLoader.class.getName());
private final ServiceOrigin serviceOrigin;
private final ServiceLoader<T> serviceLoader;

ClassPathServiceLoader(final ServiceLoader<T> serviceLoader)
ClassPathServiceLoader(final ServiceOrigin serviceOrigin, final ServiceLoader<T> serviceLoader)
{
this.serviceOrigin = serviceOrigin;
this.serviceLoader = serviceLoader;
}

static <T> Loader<T> create(final Class<T> serviceType, final ServiceOrigin serviceOrigin)
{
return new ClassPathServiceLoader<>(ServiceLoader.load(serviceType, serviceOrigin.getClassLoader()));
return new ClassPathServiceLoader<>(serviceOrigin,
ServiceLoader.load(serviceType, serviceOrigin.getClassLoader()));
}

@Override
public Stream<T> load()
{
return this.serviceLoader.stream().map(Provider::get);
return this.serviceLoader.stream().map(Provider::get)
.filter(this::filterOtherClassLoader);
}

private boolean filterOtherClassLoader(final T service)
{
final ClassLoader serviceClassLoader = service.getClass().getClassLoader();
final boolean correctClassLoader = serviceClassLoader == serviceOrigin.getClassLoader();
if (!correctClassLoader)
{
LOGGER.fine(() -> "Service " + service + " has wrong class loader: " + serviceClassLoader + ", expected "
+ serviceOrigin.getClassLoader());
}
return correctClassLoader;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ class PluginLoaderFactory
{
private static final Logger LOGGER = Logger.getLogger(PluginLoaderFactory.class.getName());
private final Path pluginsDirectory;
private final boolean searchCurrentClasspath;

PluginLoaderFactory(final Path pluginsDirectory)
PluginLoaderFactory(final Path pluginsDirectory, final boolean searchCurrentClasspath)
{
this.pluginsDirectory = pluginsDirectory;
this.searchCurrentClasspath = searchCurrentClasspath;
}

static PluginLoaderFactory createDefault()
{
return new PluginLoaderFactory(getHomeDirectory().resolve(".oft").resolve("plugins"));
return new PluginLoaderFactory(getHomeDirectory().resolve(".oft").resolve("plugins"), true);
}

private static Path getHomeDirectory()
Expand All @@ -43,7 +45,10 @@ private <T> Loader<T> createLoader(final Class<T> serviceType, final List<Servic
List<ServiceOrigin> findServiceOrigins()
{
final List<ServiceOrigin> origins = new ArrayList<>();
origins.add(ServiceOrigin.forCurrentClassPath());
if (searchCurrentClasspath)
{
origins.add(ServiceOrigin.forCurrentClassPath());
}
origins.addAll(findPluginOrigins());
LOGGER.fine(() -> "Found " + origins.size() + " service origins: " + origins + ".");
return origins;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package org.itsallcode.openfasttrace.core.serviceloader;

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.joining;

import java.net.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.List;

final class ServiceOrigin
{
private final ClassLoader classLoader;
private final List<Path> jars;

private ServiceOrigin(final ClassLoader classLoader)
private ServiceOrigin(final ClassLoader classLoader, final List<Path> jars)
{
this.classLoader = classLoader;
this.jars = jars;
}

public static ServiceOrigin forCurrentClassPath()
{
return new ServiceOrigin(getBaseClassLoader());
return new ServiceOrigin(getBaseClassLoader(), emptyList());
}

private static ClassLoader getBaseClassLoader()
Expand All @@ -32,14 +36,14 @@ public static ServiceOrigin forJar(final Path jar)

public static ServiceOrigin forJars(final List<Path> jars)
{
return new ServiceOrigin(createClassLoader(jars));
return new ServiceOrigin(createClassLoader(jars), jars);
}

private static ClassLoader createClassLoader(final List<Path> jars)
{
final URL[] urls = jars.stream().map(ServiceOrigin::toUrl)
.toArray(URL[]::new);
return new URLClassLoader(getClassLoaderName(jars), urls, getBaseClassLoader());
return new ChildFirstClassLoader(getClassLoaderName(jars), urls, getBaseClassLoader());
}

private static String getClassLoaderName(final List<Path> jars)
Expand Down Expand Up @@ -68,6 +72,6 @@ public ClassLoader getClassLoader()
@Override
public String toString()
{
return "ServiceOrigin [classLoader=" + classLoader + "]";
return "ServiceOrigin [classLoader=" + classLoader + ", jars=" + jars + "]";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ void loadingFindsNothing()

@Test
void loadingReturnsService(@Mock final ServiceLoader<DummyService> serviceLoaderMock,
@Mock final Provider<DummyService> providerMock)
@Mock final Provider<DummyService> providerMock, @Mock final ServiceOrigin originMock)
{

final DummyServiceImpl service = new DummyServiceImpl();
when(serviceLoaderMock.stream()).thenReturn(Stream.of(providerMock));
when(providerMock.get()).thenReturn(service);
final List<DummyService> services = new ClassPathServiceLoader<DummyService>(serviceLoaderMock).load().toList();
final List<DummyService> services = new ClassPathServiceLoader<DummyService>(originMock, serviceLoaderMock)
.load()
.toList();
assertAll(() -> assertThat(services, hasSize(1)),
() -> assertThat(services.get(0), not(nullValue())),
() -> assertThat(services.get(0), instanceOf(DummyServiceImpl.class)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void noPluginJar()

private PluginLoaderFactory testee()
{
return new PluginLoaderFactory(tempDir);
return new PluginLoaderFactory(tempDir, true);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
import org.junit.jupiter.api.io.TempDir;
import org.opentest4j.TestAbortedException;

/**
* Test for {@link PluginLoaderFactory} from module {@code core}. This test must
* be located in module {@code product} (which includes all plugin modules) so
* that it can access all plugin services.
*/
class PluginLoaderFactoryIT
{
@TempDir
Expand All @@ -31,7 +36,7 @@ void loadServiceFromWrongJar() throws IOException

private List<ReporterFactory> loadService()
{
return new PluginLoaderFactory(tempDir).createLoader(ReporterFactory.class).load().toList();
return new PluginLoaderFactory(tempDir, false).createLoader(ReporterFactory.class).load().toList();
}

@Test
Expand Down
10 changes: 10 additions & 0 deletions product/src/test/resources/logging.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
handlers = java.util.logging.ConsoleHandler org.itsallcode.openfasttrace.testutil.log.NoOpLoggingHandler
.level = INFO
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.encoding = UTF-8
java.util.logging.SimpleFormatter.format = %1$tF %1$tT [%4$s] %2$s - %5$s %6$s%n

org.itsallcode.openfasttrace.testutil.log.NoOpLoggingHandler.level = ALL

org.itsallcode.openfasttrace.level = INFO

0 comments on commit 7d49032

Please sign in to comment.