mirror of https://github.com/pf4j/pf4j.git
Decebal Suiu
7 years ago
24 changed files with 520 additions and 309 deletions
@ -0,0 +1,62 @@ |
|||||||
|
# PF4J Gradle Demo |
||||||
|
|
||||||
|
This demo assumes that you know the basics of Gradle (Please look at [gradle](https://gradle.org/) for more info) |
||||||
|
|
||||||
|
### Setup/Build |
||||||
|
|
||||||
|
1. Clone the repo |
||||||
|
2. Go to demo_gradle `cd demo_gradle` |
||||||
|
3. run `gradle build` |
||||||
|
|
||||||
|
* This will produce one jar, named app-plugin-demo-uberjar.jar, located in the `app/build/libs/` directory and three plugins zips located in `build/plugins` directory. |
||||||
|
* The plugins are `plugin-hello-plugin-0.0.1.zip`, `plugin-KotlinPlugin-1.0.0.zip` and `plugin-welcome-plugin-0.0.1.zip` |
||||||
|
|
||||||
|
### Run the demo |
||||||
|
|
||||||
|
1. Run |
||||||
|
|
||||||
|
``` |
||||||
|
java -jar -Dpf4j.pluginsDir=build/plugins app/build/libs/app-plugin-demo-uberjar.jar |
||||||
|
``` |
||||||
|
|
||||||
|
* pf4j.pluginsDir: is where the plugins are located |
||||||
|
|
||||||
|
2. The demo's output should look similar to: (Please see `Boot#main()` for more details) |
||||||
|
``` |
||||||
|
demo_gradle $ java -jar -Dpf4j.pluginsDir=build/plugins app/build/libs/app-plugin-demo-uberjar.jar |
||||||
|
[main] INFO org.pf4j.demo.Boot - ######################################## |
||||||
|
[main] INFO org.pf4j.demo.Boot - PF4J-DEMO |
||||||
|
[main] INFO org.pf4j.demo.Boot - ######################################## |
||||||
|
[main] INFO org.pf4j.DefaultPluginStatusProvider - Enabled plugins: [] |
||||||
|
[main] INFO org.pf4j.DefaultPluginStatusProvider - Disabled plugins: [] |
||||||
|
[main] INFO org.pf4j.DefaultPluginManager - PF4J version 0.0.0 in 'deployment' mode |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Plugin 'welcome-plugin@0.0.1' resolved |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Plugin 'KotlinPlugin@1.0.0' resolved |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Plugin 'hello-plugin@0.0.1' resolved |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Start plugin 'welcome-plugin@0.0.1' |
||||||
|
[main] INFO org.pf4j.demo.welcome.WelcomePlugin - WelcomePlugin.start() |
||||||
|
[main] INFO org.pf4j.demo.welcome.WelcomePlugin - WELCOMEPLUGIN |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Start plugin 'KotlinPlugin@1.0.0' |
||||||
|
[main] INFO org.pf4j.demo.kotlin.KotlinPlugin - KotlinPlugin.start() |
||||||
|
[main] INFO org.pf4j.demo.kotlin.KotlinPlugin - KOTLINPLUGIN |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Start plugin 'hello-plugin@0.0.1' |
||||||
|
[main] INFO org.pf4j.demo.hello.HelloPlugin - HelloPlugin.start() |
||||||
|
[main] INFO org.pf4j.demo.Boot - Plugindirectory: |
||||||
|
[main] INFO org.pf4j.demo.Boot - build/plugins |
||||||
|
|
||||||
|
[main] INFO org.pf4j.demo.Boot - Found 3 extensions for extension point 'org.pf4j.demo.api.Greeting' |
||||||
|
[main] INFO org.pf4j.demo.Boot - >>> Whazzup |
||||||
|
[main] INFO org.pf4j.demo.Boot - >>> Welcome |
||||||
|
[main] INFO org.pf4j.demo.Boot - >>> Hello |
||||||
|
[main] INFO org.pf4j.demo.Boot - Extensions added by plugin 'welcome-plugin': |
||||||
|
[main] INFO org.pf4j.demo.Boot - Extensions added by plugin 'KotlinPlugin': |
||||||
|
[main] INFO org.pf4j.demo.Boot - Extensions added by plugin 'hello-plugin': |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Stop plugin 'hello-plugin@0.0.1' |
||||||
|
[main] INFO org.pf4j.demo.hello.HelloPlugin - HelloPlugin.stop() |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Stop plugin 'KotlinPlugin@1.0.0' |
||||||
|
[main] INFO org.pf4j.demo.kotlin.KotlinPlugin - KotlinPlugin.stop() |
||||||
|
[main] INFO org.pf4j.AbstractPluginManager - Stop plugin 'welcome-plugin@0.0.1' |
||||||
|
[main] INFO org.pf4j.demo.welcome.WelcomePlugin - WelcomePlugin.stop() |
||||||
|
|
||||||
|
``` |
||||||
|
|
@ -1,5 +1,6 @@ |
|||||||
dependencies { |
dependencies { |
||||||
compile 'org.pf4j:pf4j:2.0.0-SNAPSHOT' |
compile group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}" |
||||||
compile 'org.apache.commons:commons-lang3:3.0' |
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.5' |
||||||
|
|
||||||
testCompile group: 'junit', name: 'junit', version: '4.+' |
testCompile group: 'junit', name: 'junit', version: '4.+' |
||||||
} |
} |
||||||
|
@ -0,0 +1,32 @@ |
|||||||
|
subprojects { |
||||||
|
jar { |
||||||
|
manifest { |
||||||
|
attributes 'Plugin-Class': "${pluginClass}", |
||||||
|
'Plugin-Id': "${pluginId}", |
||||||
|
'Plugin-Version': "${version}", |
||||||
|
'Plugin-Provider': "${pluginProvider}" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
task plugin(type: Jar) { |
||||||
|
baseName = "plugin-${pluginId}" |
||||||
|
into('classes') { |
||||||
|
with jar |
||||||
|
} |
||||||
|
into('lib') { |
||||||
|
from configurations.compile |
||||||
|
} |
||||||
|
extension('zip') |
||||||
|
} |
||||||
|
|
||||||
|
task assemblePlugin(type: Copy) { |
||||||
|
from plugin |
||||||
|
into pluginsDir |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
task assemblePlugins(type: Copy) { |
||||||
|
dependsOn subprojects.assemblePlugin |
||||||
|
} |
||||||
|
|
||||||
|
build.dependsOn project.tasks.assemblePlugins |
@ -1,33 +1,9 @@ |
|||||||
jar { |
|
||||||
baseName = 'WelcomePlugin' |
|
||||||
version = '0.1.0' |
|
||||||
manifest { |
|
||||||
attributes 'Plugin-Class': 'org.pf4j.demo.welcome.WelcomePlugin', |
|
||||||
'Plugin-Id': 'WelcomePlugin', |
|
||||||
'Plugin-Version': '1.0.0', |
|
||||||
'Plugin-Provider': 'Decebal Suiu' |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
task plugin(type: Jar) { |
|
||||||
baseName = 'WelcomePlugin' |
|
||||||
version = '0.1.0' |
|
||||||
into('classes') { |
|
||||||
with jar |
|
||||||
} |
|
||||||
into('lib') { |
|
||||||
from configurations.compile |
|
||||||
} |
|
||||||
extension('zip') |
|
||||||
} |
|
||||||
assemble.dependsOn plugin |
|
||||||
|
|
||||||
dependencies { |
dependencies { |
||||||
compileOnly project(':api') |
|
||||||
// compileOnly important!!! We do not want to put the api into the zip file since the main program has it already! |
// compileOnly important!!! We do not want to put the api into the zip file since the main program has it already! |
||||||
compile('org.pf4j:pf4j:2.0.0-SNAPSHOT') { |
compileOnly project(':api') |
||||||
|
compileOnly(group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}") { |
||||||
exclude group: "org.slf4j" |
exclude group: "org.slf4j" |
||||||
} |
} |
||||||
compile 'org.apache.commons:commons-lang3:3.5' |
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.5' |
||||||
testCompile group: 'junit', name: 'junit', version: '4.+' |
testCompile group: 'junit', name: 'junit', version: '4.+' |
||||||
} |
} |
||||||
|
@ -0,0 +1,6 @@ |
|||||||
|
version=0.0.1 |
||||||
|
|
||||||
|
pluginId=welcome-plugin |
||||||
|
pluginClass=org.pf4j.demo.welcome.WelcomePlugin |
||||||
|
pluginProvider=Decebal Suiu |
||||||
|
pluginDependencies= |
@ -1,5 +0,0 @@ |
|||||||
plugin.id=welcome-plugin |
|
||||||
plugin.class=org.pf4j.demo.welcome.WelcomePlugin |
|
||||||
plugin.version=0.0.1 |
|
||||||
plugin.provider=Decebal Suiu |
|
||||||
plugin.dependencies= |
|
@ -1,33 +1,9 @@ |
|||||||
jar { |
|
||||||
baseName = 'HelloPlugin' |
|
||||||
version = '0.1.0' |
|
||||||
manifest { |
|
||||||
attributes 'Plugin-Class': 'org.pf4j.demo.hello.HelloPlugin', |
|
||||||
'Plugin-Id': 'HelloPlugin', |
|
||||||
'Plugin-Version': '1.0.0', |
|
||||||
'Plugin-Provider': 'Decebal Suiu' |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
task plugin(type: Jar) { |
|
||||||
baseName = 'HelloPlugin' |
|
||||||
version = '0.1.0' |
|
||||||
into('classes') { |
|
||||||
with jar |
|
||||||
} |
|
||||||
into('lib') { |
|
||||||
from configurations.compile |
|
||||||
} |
|
||||||
extension('zip') |
|
||||||
} |
|
||||||
assemble.dependsOn plugin |
|
||||||
|
|
||||||
dependencies { |
dependencies { |
||||||
compileOnly project(':api') |
|
||||||
// compileOnly important!!! We do not want to put the api into the zip file since the main program has it already! |
// compileOnly important!!! We do not want to put the api into the zip file since the main program has it already! |
||||||
compile('org.pf4j:pf4j:2.0.0-SNAPSHOT') { |
compileOnly project(':api') |
||||||
|
compileOnly(group: 'org.pf4j', name: 'pf4j', version: "${pf4jVersion}") { |
||||||
exclude group: "org.slf4j" |
exclude group: "org.slf4j" |
||||||
} |
} |
||||||
compile 'org.apache.commons:commons-lang3:3.5' |
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.5' |
||||||
testCompile group: 'junit', name: 'junit', version: '4.+' |
testCompile group: 'junit', name: 'junit', version: '4.+' |
||||||
} |
} |
||||||
|
@ -0,0 +1,6 @@ |
|||||||
|
version=0.0.1 |
||||||
|
|
||||||
|
pluginId=hello-plugin |
||||||
|
pluginClass=org.pf4j.demo.hello.HelloPlugin |
||||||
|
pluginProvider=Decebal Suiu |
||||||
|
pluginDependencies= |
@ -1,5 +0,0 @@ |
|||||||
plugin.id=hello-plugin |
|
||||||
plugin.class=org.pf4j.demo.hello.HelloPlugin |
|
||||||
plugin.version=0.0.1 |
|
||||||
plugin.provider=Decebal Suiu |
|
||||||
plugin.dependencies= |
|
@ -0,0 +1,6 @@ |
|||||||
|
version=1.0.0 |
||||||
|
|
||||||
|
pluginId=KotlinPlugin |
||||||
|
pluginClass=org.pf4j.demo.kotlin.KotlinPlugin |
||||||
|
pluginProvider=Anindya Chatterjee |
||||||
|
pluginDependencies= |
@ -1,5 +1,8 @@ |
|||||||
include 'api' |
include 'api' |
||||||
include 'app' |
include 'app' |
||||||
include 'plugins/plugin1' |
|
||||||
include 'plugins/plugin2' |
include 'plugins' |
||||||
include 'plugins/plugin3' |
|
||||||
|
include 'plugins:plugin1' |
||||||
|
include 'plugins:plugin2' |
||||||
|
include 'plugins:plugin3' |
||||||
|
@ -0,0 +1,211 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012 Decebal Suiu |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.pf4j; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* A plugin descriptor contains information about a plug-in obtained |
||||||
|
* from the manifest (META-INF) file. |
||||||
|
* |
||||||
|
* @author Decebal Suiu |
||||||
|
*/ |
||||||
|
public class DefaultPluginDescriptor implements PluginDescriptor { |
||||||
|
|
||||||
|
private String pluginId; |
||||||
|
private String pluginDescription; |
||||||
|
private String pluginClass; |
||||||
|
private String version; |
||||||
|
private String requires = "*"; // SemVer format
|
||||||
|
private String provider; |
||||||
|
private List<PluginDependency> dependencies; |
||||||
|
private String license; |
||||||
|
|
||||||
|
public DefaultPluginDescriptor() { |
||||||
|
dependencies = new ArrayList<>(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param pluginId |
||||||
|
* @param pluginDescription |
||||||
|
* @param pluginClass |
||||||
|
* @param version |
||||||
|
* @param requires |
||||||
|
* @param provider |
||||||
|
* @param license |
||||||
|
*/ |
||||||
|
public DefaultPluginDescriptor(String pluginId, String pluginDescription, String pluginClass, String version, String requires, String provider, String license) { |
||||||
|
this(); |
||||||
|
this.pluginId = pluginId; |
||||||
|
this.pluginDescription = pluginDescription; |
||||||
|
this.pluginClass = pluginClass; |
||||||
|
this.version = version; |
||||||
|
this.requires = requires; |
||||||
|
this.provider = provider; |
||||||
|
this.license = license; |
||||||
|
} |
||||||
|
|
||||||
|
public void addDependency(PluginDependency dependency) { |
||||||
|
this.dependencies.add(dependency); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the unique identifier of this plugin. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String getPluginId() { |
||||||
|
return pluginId; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the description of this plugin. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String getPluginDescription() { |
||||||
|
return pluginDescription; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the name of the class that implements Plugin interface. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String getPluginClass() { |
||||||
|
return pluginClass; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the version of this plugin. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String getVersion() { |
||||||
|
return version; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns string version of requires |
||||||
|
* |
||||||
|
* @return String with requires expression on SemVer format |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String getRequires() { |
||||||
|
return requires; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the provider name of this plugin. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String getProvider() { |
||||||
|
return provider; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the legal license of this plugin, e.g. "Apache-2.0", "MIT" etc |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String getLicense() { |
||||||
|
return license; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all dependencies declared by this plugin. |
||||||
|
* Returns an empty array if this plugin does not declare any require. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public List<PluginDependency> getDependencies() { |
||||||
|
return dependencies; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "PluginDescriptor [pluginId=" + pluginId + ", pluginClass=" |
||||||
|
+ pluginClass + ", version=" + version + ", provider=" |
||||||
|
+ provider + ", dependencies=" + dependencies + ", description=" |
||||||
|
+ pluginDescription + ", requires=" + requires + ", license=" |
||||||
|
+ license + "]"; |
||||||
|
} |
||||||
|
|
||||||
|
protected DefaultPluginDescriptor setPluginId(String pluginId) { |
||||||
|
this.pluginId = pluginId; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
protected PluginDescriptor setPluginDescription(String pluginDescription) { |
||||||
|
this.pluginDescription = pluginDescription; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
protected PluginDescriptor setPluginClass(String pluginClassName) { |
||||||
|
this.pluginClass = pluginClassName; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
protected DefaultPluginDescriptor setPluginVersion(String version) { |
||||||
|
this.version = version; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
protected PluginDescriptor setProvider(String provider) { |
||||||
|
this.provider = provider; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
protected PluginDescriptor setRequires(String requires) { |
||||||
|
this.requires = requires; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
protected PluginDescriptor setDependencies(String dependencies) { |
||||||
|
if (dependencies != null) { |
||||||
|
dependencies = dependencies.trim(); |
||||||
|
if (dependencies.isEmpty()) { |
||||||
|
this.dependencies = Collections.emptyList(); |
||||||
|
} else { |
||||||
|
this.dependencies = new ArrayList<>(); |
||||||
|
String[] tokens = dependencies.split(","); |
||||||
|
for (String dependency : tokens) { |
||||||
|
dependency = dependency.trim(); |
||||||
|
if (!dependency.isEmpty()) { |
||||||
|
this.dependencies.add(new PluginDependency(dependency)); |
||||||
|
} |
||||||
|
} |
||||||
|
if (this.dependencies.isEmpty()) { |
||||||
|
this.dependencies = Collections.emptyList(); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
this.dependencies = Collections.emptyList(); |
||||||
|
} |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public PluginDescriptor setLicense(String license) { |
||||||
|
this.license = license; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2015 Decebal Suiu |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.pf4j.processor; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import javax.annotation.processing.Filer; |
||||||
|
import javax.tools.FileObject; |
||||||
|
import javax.tools.StandardLocation; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.StringReader; |
||||||
|
import java.util.*; |
||||||
|
|
||||||
|
import static org.hamcrest.core.Is.is; |
||||||
|
import static org.junit.Assert.*; |
||||||
|
import static org.mockito.BDDMockito.given; |
||||||
|
import static org.mockito.Matchers.any; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Josiah Haswell |
||||||
|
*/ |
||||||
|
public class ServiceProviderExtensionStorageTest { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void ensureServiceProviderExtensionStorageReadWorks() throws IOException { |
||||||
|
final StringReader file = new StringReader("#hello\n World"); |
||||||
|
final Set<String> entries = new HashSet<>(); |
||||||
|
ServiceProviderExtensionStorage.read(file, entries); |
||||||
|
|
||||||
|
assertThat(entries.size(), is(1)); |
||||||
|
assertThat(entries.contains("World"), is(true)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void ensureReadingExtensionsProducesCorrectListOfExtensions() { |
||||||
|
final StringReader file = new StringReader("#hello\n World"); |
||||||
|
final ExtensionAnnotationProcessor processor = mock(ExtensionAnnotationProcessor.class); |
||||||
|
final Map<String, Set<String>> extensions = new HashMap<>(); |
||||||
|
extensions.put("hello", Collections.singleton("world")); |
||||||
|
|
||||||
|
given(processor.getExtensions()).willReturn(extensions); |
||||||
|
ServiceProviderExtensionStorage extensionStorage = new ServiceProviderExtensionStorage(processor) { |
||||||
|
@Override |
||||||
|
protected Filer getFiler() { |
||||||
|
try { |
||||||
|
Filer filer = mock(Filer.class); |
||||||
|
FileObject fileObject = mock(FileObject.class); |
||||||
|
given(fileObject.openReader(true)).willReturn(file); |
||||||
|
given(filer.getResource( |
||||||
|
any(StandardLocation.class), |
||||||
|
any(String.class), |
||||||
|
any(String.class) |
||||||
|
)).willReturn(fileObject); |
||||||
|
return filer; |
||||||
|
} catch(IOException ex) { |
||||||
|
throw new IllegalStateException("Shouldn't have gotten here"); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Map<String, Set<String>> read = extensionStorage.read(); |
||||||
|
assertThat(read.containsKey("hello"), is(true)); |
||||||
|
assertThat(read.get("hello"), is(Collections.singleton("World"))); |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue