Skip to content

Commit adddfa2

Browse files
authored
Add one test for plugin type to PluginsLoaderTests (#117725)
* Add one test for plugin type to PluginsLoaderTests * Suppress ExtraFs (or PluginsUtils etc could fail with extra0 files)
1 parent 95315cc commit adddfa2

File tree

2 files changed

+252
-3
lines changed

2 files changed

+252
-3
lines changed

server/src/main/java/org/elasticsearch/plugins/PluginsLoader.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ static String toModuleName(String name) {
416416
return result;
417417
}
418418

419-
static final String toPackageName(String className) {
419+
static String toPackageName(String className) {
420420
assert className.endsWith(".") == false;
421421
int index = className.lastIndexOf('.');
422422
if (index == -1) {
@@ -426,11 +426,11 @@ static final String toPackageName(String className) {
426426
}
427427

428428
@SuppressForbidden(reason = "I need to convert URL's to Paths")
429-
static final Path[] urlsToPaths(Set<URL> urls) {
429+
static Path[] urlsToPaths(Set<URL> urls) {
430430
return urls.stream().map(PluginsLoader::uncheckedToURI).map(PathUtils::get).toArray(Path[]::new);
431431
}
432432

433-
static final URI uncheckedToURI(URL url) {
433+
static URI uncheckedToURI(URL url) {
434434
try {
435435
return url.toURI();
436436
} catch (URISyntaxException e) {

server/src/test/java/org/elasticsearch/plugins/PluginsLoaderTests.java

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,45 @@
99

1010
package org.elasticsearch.plugins;
1111

12+
import org.apache.lucene.tests.util.LuceneTestCase;
13+
import org.elasticsearch.Version;
14+
import org.elasticsearch.common.settings.Settings;
15+
import org.elasticsearch.env.Environment;
16+
import org.elasticsearch.env.TestEnvironment;
17+
import org.elasticsearch.logging.LogManager;
18+
import org.elasticsearch.logging.Logger;
19+
import org.elasticsearch.plugin.analysis.CharFilterFactory;
1220
import org.elasticsearch.test.ESTestCase;
21+
import org.elasticsearch.test.PrivilegedOperations;
22+
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
23+
import org.elasticsearch.test.jar.JarUtils;
1324

25+
import java.io.IOException;
26+
import java.io.UncheckedIOException;
27+
import java.net.URLClassLoader;
28+
import java.nio.file.Files;
29+
import java.nio.file.Path;
30+
import java.util.Map;
31+
32+
import static java.util.Map.entry;
33+
import static org.elasticsearch.test.LambdaMatchers.transformedMatch;
34+
import static org.hamcrest.Matchers.contains;
1435
import static org.hamcrest.Matchers.equalTo;
36+
import static org.hamcrest.Matchers.hasSize;
37+
import static org.hamcrest.Matchers.instanceOf;
38+
import static org.hamcrest.Matchers.is;
39+
import static org.hamcrest.Matchers.not;
1540

41+
@ESTestCase.WithoutSecurityManager
42+
@LuceneTestCase.SuppressFileSystems(value = "ExtrasFS")
1643
public class PluginsLoaderTests extends ESTestCase {
1744

45+
private static final Logger logger = LogManager.getLogger(PluginsLoaderTests.class);
46+
47+
static PluginsLoader newPluginsLoader(Settings settings) {
48+
return PluginsLoader.createPluginsLoader(null, TestEnvironment.newEnvironment(settings).pluginsFile(), false);
49+
}
50+
1851
public void testToModuleName() {
1952
assertThat(PluginsLoader.toModuleName("module.name"), equalTo("module.name"));
2053
assertThat(PluginsLoader.toModuleName("module-name"), equalTo("module.name"));
@@ -28,4 +61,220 @@ public void testToModuleName() {
2861
assertThat(PluginsLoader.toModuleName("_module_name"), equalTo("_module_name"));
2962
assertThat(PluginsLoader.toModuleName("_"), equalTo("_"));
3063
}
64+
65+
public void testStablePluginLoading() throws Exception {
66+
final Path home = createTempDir();
67+
final Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), home).build();
68+
final Path plugins = home.resolve("plugins");
69+
final Path plugin = plugins.resolve("stable-plugin");
70+
Files.createDirectories(plugin);
71+
PluginTestUtil.writeStablePluginProperties(
72+
plugin,
73+
"description",
74+
"description",
75+
"name",
76+
"stable-plugin",
77+
"version",
78+
"1.0.0",
79+
"elasticsearch.version",
80+
Version.CURRENT.toString(),
81+
"java.version",
82+
System.getProperty("java.specification.version")
83+
);
84+
85+
Path jar = plugin.resolve("impl.jar");
86+
JarUtils.createJarWithEntries(jar, Map.of("p/A.class", InMemoryJavaCompiler.compile("p.A", """
87+
package p;
88+
import java.util.Map;
89+
import org.elasticsearch.plugin.analysis.CharFilterFactory;
90+
import org.elasticsearch.plugin.NamedComponent;
91+
import java.io.Reader;
92+
@NamedComponent( "a_name")
93+
public class A implements CharFilterFactory {
94+
@Override
95+
public Reader create(Reader reader) {
96+
return reader;
97+
}
98+
}
99+
""")));
100+
Path namedComponentFile = plugin.resolve("named_components.json");
101+
Files.writeString(namedComponentFile, """
102+
{
103+
"org.elasticsearch.plugin.analysis.CharFilterFactory": {
104+
"a_name": "p.A"
105+
}
106+
}
107+
""");
108+
109+
var pluginsLoader = newPluginsLoader(settings);
110+
try {
111+
var loadedLayers = pluginsLoader.pluginLayers().toList();
112+
113+
assertThat(loadedLayers, hasSize(1));
114+
assertThat(loadedLayers.get(0).pluginBundle().pluginDescriptor().getName(), equalTo("stable-plugin"));
115+
assertThat(loadedLayers.get(0).pluginBundle().pluginDescriptor().isStable(), is(true));
116+
117+
assertThat(pluginsLoader.pluginDescriptors(), hasSize(1));
118+
assertThat(pluginsLoader.pluginDescriptors().get(0).getName(), equalTo("stable-plugin"));
119+
assertThat(pluginsLoader.pluginDescriptors().get(0).isStable(), is(true));
120+
121+
var pluginClassLoader = loadedLayers.get(0).pluginClassLoader();
122+
var pluginModuleLayer = loadedLayers.get(0).pluginModuleLayer();
123+
assertThat(pluginClassLoader, instanceOf(UberModuleClassLoader.class));
124+
assertThat(pluginModuleLayer, is(not(ModuleLayer.boot())));
125+
assertThat(pluginModuleLayer.modules(), contains(transformedMatch(Module::getName, equalTo("synthetic.stable.plugin"))));
126+
127+
if (CharFilterFactory.class.getModule().isNamed() == false) {
128+
// test frameworks run with stable api classes on classpath, so we
129+
// have no choice but to let our class read the unnamed module that
130+
// owns the stable api classes
131+
((UberModuleClassLoader) pluginClassLoader).addReadsSystemClassLoaderUnnamedModule();
132+
}
133+
134+
Class<?> stableClass = pluginClassLoader.loadClass("p.A");
135+
assertThat(stableClass.getModule().getName(), equalTo("synthetic.stable.plugin"));
136+
} finally {
137+
closePluginLoaders(pluginsLoader);
138+
}
139+
}
140+
141+
public void testModularPluginLoading() throws Exception {
142+
final Path home = createTempDir();
143+
final Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), home).build();
144+
final Path plugins = home.resolve("plugins");
145+
final Path plugin = plugins.resolve("modular-plugin");
146+
Files.createDirectories(plugin);
147+
PluginTestUtil.writePluginProperties(
148+
plugin,
149+
"description",
150+
"description",
151+
"name",
152+
"modular-plugin",
153+
"classname",
154+
"p.A",
155+
"modulename",
156+
"modular.plugin",
157+
"version",
158+
"1.0.0",
159+
"elasticsearch.version",
160+
Version.CURRENT.toString(),
161+
"java.version",
162+
System.getProperty("java.specification.version")
163+
);
164+
165+
Path jar = plugin.resolve("impl.jar");
166+
Map<String, CharSequence> sources = Map.ofEntries(entry("module-info", "module modular.plugin { exports p; }"), entry("p.A", """
167+
package p;
168+
import org.elasticsearch.plugins.Plugin;
169+
170+
public class A extends Plugin {
171+
}
172+
"""));
173+
174+
// Usually org.elasticsearch.plugins.Plugin would be in the org.elasticsearch.server module.
175+
// Unfortunately, as tests run non-modular, it will be in the unnamed module, so we need to add a read for it.
176+
var classToBytes = InMemoryJavaCompiler.compile(sources, "--add-reads", "modular.plugin=ALL-UNNAMED");
177+
178+
JarUtils.createJarWithEntries(
179+
jar,
180+
Map.ofEntries(entry("module-info.class", classToBytes.get("module-info")), entry("p/A.class", classToBytes.get("p.A")))
181+
);
182+
183+
var pluginsLoader = newPluginsLoader(settings);
184+
try {
185+
var loadedLayers = pluginsLoader.pluginLayers().toList();
186+
187+
assertThat(loadedLayers, hasSize(1));
188+
assertThat(loadedLayers.get(0).pluginBundle().pluginDescriptor().getName(), equalTo("modular-plugin"));
189+
assertThat(loadedLayers.get(0).pluginBundle().pluginDescriptor().isStable(), is(false));
190+
assertThat(loadedLayers.get(0).pluginBundle().pluginDescriptor().isModular(), is(true));
191+
192+
assertThat(pluginsLoader.pluginDescriptors(), hasSize(1));
193+
assertThat(pluginsLoader.pluginDescriptors().get(0).getName(), equalTo("modular-plugin"));
194+
assertThat(pluginsLoader.pluginDescriptors().get(0).isModular(), is(true));
195+
196+
var pluginModuleLayer = loadedLayers.get(0).pluginModuleLayer();
197+
assertThat(pluginModuleLayer, is(not(ModuleLayer.boot())));
198+
assertThat(pluginModuleLayer.modules(), contains(transformedMatch(Module::getName, equalTo("modular.plugin"))));
199+
} finally {
200+
closePluginLoaders(pluginsLoader);
201+
}
202+
}
203+
204+
public void testNonModularPluginLoading() throws Exception {
205+
final Path home = createTempDir();
206+
final Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), home).build();
207+
final Path plugins = home.resolve("plugins");
208+
final Path plugin = plugins.resolve("non-modular-plugin");
209+
Files.createDirectories(plugin);
210+
PluginTestUtil.writePluginProperties(
211+
plugin,
212+
"description",
213+
"description",
214+
"name",
215+
"non-modular-plugin",
216+
"classname",
217+
"p.A",
218+
"version",
219+
"1.0.0",
220+
"elasticsearch.version",
221+
Version.CURRENT.toString(),
222+
"java.version",
223+
System.getProperty("java.specification.version")
224+
);
225+
226+
Path jar = plugin.resolve("impl.jar");
227+
Map<String, CharSequence> sources = Map.ofEntries(entry("p.A", """
228+
package p;
229+
import org.elasticsearch.plugins.Plugin;
230+
231+
public class A extends Plugin {
232+
}
233+
"""));
234+
235+
var classToBytes = InMemoryJavaCompiler.compile(sources);
236+
237+
JarUtils.createJarWithEntries(jar, Map.ofEntries(entry("p/A.class", classToBytes.get("p.A"))));
238+
239+
var pluginsLoader = newPluginsLoader(settings);
240+
try {
241+
var loadedLayers = pluginsLoader.pluginLayers().toList();
242+
243+
assertThat(loadedLayers, hasSize(1));
244+
assertThat(loadedLayers.get(0).pluginBundle().pluginDescriptor().getName(), equalTo("non-modular-plugin"));
245+
assertThat(loadedLayers.get(0).pluginBundle().pluginDescriptor().isStable(), is(false));
246+
assertThat(loadedLayers.get(0).pluginBundle().pluginDescriptor().isModular(), is(false));
247+
248+
assertThat(pluginsLoader.pluginDescriptors(), hasSize(1));
249+
assertThat(pluginsLoader.pluginDescriptors().get(0).getName(), equalTo("non-modular-plugin"));
250+
assertThat(pluginsLoader.pluginDescriptors().get(0).isModular(), is(false));
251+
252+
var pluginModuleLayer = loadedLayers.get(0).pluginModuleLayer();
253+
assertThat(pluginModuleLayer, is(ModuleLayer.boot()));
254+
} finally {
255+
closePluginLoaders(pluginsLoader);
256+
}
257+
}
258+
259+
// Closes the URLClassLoaders and UberModuleClassloaders created by the given plugin loader.
260+
// We can use the direct ClassLoader from the plugin because tests do not use any parent SPI ClassLoaders.
261+
static void closePluginLoaders(PluginsLoader pluginsLoader) {
262+
pluginsLoader.pluginLayers().forEach(lp -> {
263+
if (lp.pluginClassLoader() instanceof URLClassLoader urlClassLoader) {
264+
try {
265+
PrivilegedOperations.closeURLClassLoader(urlClassLoader);
266+
} catch (IOException unexpected) {
267+
throw new UncheckedIOException(unexpected);
268+
}
269+
} else if (lp.pluginClassLoader() instanceof UberModuleClassLoader loader) {
270+
try {
271+
PrivilegedOperations.closeURLClassLoader(loader.getInternalLoader());
272+
} catch (Exception e) {
273+
throw new RuntimeException(e);
274+
}
275+
} else {
276+
logger.info("Cannot close unexpected classloader " + lp.pluginClassLoader());
277+
}
278+
});
279+
}
31280
}

0 commit comments

Comments
 (0)