Fix config lateinit errors and fix testing

This commit is contained in:
Norbi Peti 2023-06-26 23:03:19 +02:00
parent 5e39b6ef57
commit 529fef1c5f
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
8 changed files with 81 additions and 79 deletions

View file

@ -28,10 +28,13 @@ import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.ConsoleCommandSender
import org.bukkit.entity.Player
import org.bukkit.plugin.PluginDescriptionFile
import org.bukkit.plugin.java.JavaPluginLoader
import java.io.File
import java.util.*
import java.util.function.Supplier
class MainPlugin : ButtonPlugin() {
class MainPlugin : ButtonPlugin {
private var economy: Economy? = null
/**
@ -62,6 +65,10 @@ class MainPlugin : ButtonPlugin() {
*/ // TODO: Combine the channel access test with command permissions (with a generic implementation)
val externalPlayerPermissionGroup get() = iConfig.getData("externalPlayerPermissionGroup", "default")
constructor() : super()
constructor(loader: JavaPluginLoader, description: PluginDescriptionFile, dataFolder: File, file: File) : super(loader, description, dataFolder, file)
public override fun pluginEnable() {
instance = this
val pdf = description

View file

@ -1,50 +1,21 @@
package buttondevteam.core;
package buttondevteam.core
import buttondevteam.core.component.channel.Channel;
import buttondevteam.core.component.channel.ChannelComponent;
import buttondevteam.lib.ChromaUtils;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.chat.Color;
import buttondevteam.lib.chat.TBMCChatAPI;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import be.seeseemelk.mockbukkit.MockBukkit
import buttondevteam.core.component.channel.Channel
import buttondevteam.core.component.channel.ChannelComponent
import buttondevteam.lib.ChromaUtils.isTest
import buttondevteam.lib.architecture.Component.Companion.registerComponent
import buttondevteam.lib.chat.Color
import buttondevteam.lib.chat.TBMCChatAPI.registerChatChannel
import org.bukkit.ChatColor
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Logger;
public class TestPrepare {
public static void PrepareServer() {
ChromaUtils.setTest(true); //Needs to be in a separate class because of the potential lack of Mockito
Bukkit.setServer(Mockito.mock(Server.class, new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) {
if (returns(invocation, String.class))
return "test";
if (returns(invocation, Logger.class))
return Logger.getAnonymousLogger();
if (returns(invocation, PluginManager.class))
return Mockito.mock(PluginManager.class);
if (returns(invocation, Collection.class))
return Collections.EMPTY_LIST;
if (returns(invocation, BukkitScheduler.class))
return Mockito.mock(BukkitScheduler.class);
return null;
}
boolean returns(InvocationOnMock invocation, Class<?> cl) {
return cl.isAssignableFrom(invocation.getMethod().getReturnType());
}
}));
Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent());
TBMCChatAPI.registerChatChannel(Channel.globalChat = new Channel("${ChatColor.WHITE}g${ChatColor.WHITE}", Color.White, "g", null));
}
@Deprecated("Use MockBukkit")
object TestPrepare {
@JvmStatic
fun prepareServer() {
isTest = true //Needs to be in a separate class because of the potential lack of Mockito
MockBukkit.mock()
registerComponent(MockBukkit.load(MainPlugin::class.java), ChannelComponent())
registerChatChannel(Channel("${ChatColor.WHITE}g${ChatColor.WHITE}", Color.White, "g", null).also { Channel.globalChat = it })
}
}

View file

@ -70,6 +70,17 @@ object ChromaUtils {
} else what.get()
}
/**
* Log a warning message if the plugin is initialized. If not, just print a regular message.
*/
fun logWarn(message: String) {
if (MainPlugin.isInitialized) {
MainPlugin.instance.logger.warning(message)
} else {
println(message)
}
}
/**
* Returns true while unit testing.
*/

View file

@ -8,14 +8,16 @@ import buttondevteam.lib.chat.ICommand2MC
import org.bukkit.configuration.InvalidConfigurationException
import org.bukkit.configuration.file.FileConfiguration
import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.plugin.PluginDescriptionFile
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.plugin.java.JavaPluginLoader
import java.io.File
import java.io.IOException
import java.util.*
import java.util.function.Consumer
@HasConfig(global = true)
abstract class ButtonPlugin : JavaPlugin() {
abstract class ButtonPlugin : JavaPlugin {
protected val iConfig = IHaveConfig(::saveConfig, section)
private var yaml: YamlConfiguration? = null
@ -31,6 +33,11 @@ abstract class ButtonPlugin : JavaPlugin() {
* Used to unregister components in the right order - and to reload configs
*/
val componentStack = Stack<Component<*>>()
// Support testing with the protected constructor (MockBukkit)
constructor() : super()
protected constructor(loader: JavaPluginLoader, description: PluginDescriptionFile, dataFolder: File, file: File) : super(loader, description, dataFolder, file)
protected abstract fun pluginEnable()
/**

View file

@ -19,7 +19,7 @@ import java.util.stream.Collectors
abstract class Component<TP : JavaPlugin> {
var isEnabled = false
lateinit var config: IHaveConfig
var config: IHaveConfig = IHaveConfig({ logWarn("Attempted to save config with null section!") }, null)
private set
lateinit var plugin: TP
private set
@ -100,7 +100,11 @@ abstract class Component<TP : JavaPlugin> {
* @return A map containing configs
*/
fun getConfigMap(key: String, defaultProvider: Map<String, Consumer<IHaveConfig>>): Map<String, IHaveConfig> {
val c: ConfigurationSection = config.config
val c: ConfigurationSection? = config.config
if (c == null) {
logWarn("Config section is null when getting config map")
return defaultProvider.mapValues { kv -> IHaveConfig(plugin::saveConfig, null).also { kv.value.accept(it) } }
}
val cs = c.getConfigurationSection(key) ?: c.createSection(key)
val res = cs.getValues(false).entries.stream()
.filter { (_, value) -> value is ConfigurationSection }
@ -109,11 +113,7 @@ abstract class Component<TP : JavaPlugin> {
{ (_, value) -> IHaveConfig(plugin::saveConfig, value as ConfigurationSection) }
))
if (res.isEmpty()) {
for ((key1, value) in defaultProvider) {
val conf = IHaveConfig(plugin::saveConfig, cs.createSection(key1))
value.accept(conf)
res[key1] = conf
}
defaultProvider.mapValuesTo(res) { kv -> IHaveConfig(plugin::saveConfig, cs.createSection(kv.key)).also { kv.value.accept(it) } }
}
return res
}
@ -121,8 +121,7 @@ abstract class Component<TP : JavaPlugin> {
private val className: String get() = javaClass.simpleName
internal fun updateConfig() {
if (!this::config.isInitialized) this.config = IHaveConfig(plugin::saveConfig, getConfigSection())
else this.config.reload(getConfigSection())
this.config.reload(getConfigSection(), plugin::saveConfig)
}
private fun getConfigSection(): ConfigurationSection {

View file

@ -14,16 +14,16 @@ import java.util.function.Function
* Use [Component.config] or [ButtonPlugin.iConfig] then [IHaveConfig.getData] to get an instance.
*
* **Note:** The instance can become outdated if the config is reloaded.
* @param config May be null for testing
* @param config The config object to use for the whole file
* @param path The path to the config value
* @param primitiveDef The default value, as stored in the config. Non-nullable as it needs to be saved to the config
* @param primitiveDef The default value, as stored in the config. Non-nullable as it needs to be saved sto the config
* @param getter Function to convert primtive types to [T]. The parameter is of a primitive type as returned by [Configuration.get]
* @param setter Function to convert [T] to a primitive type. The result should be a primitive type or string that can be retrieved correctly later
* @param readOnly If true, changing the value will have no effect
* @param T The type of the config value. May be nullable if the getter cannot always return a value
*/
class ConfigData<T : Any?> internal constructor(
var config: IHaveConfig?,
val config: IHaveConfig,
override val path: String,
private val primitiveDef: Any,
private val getter: Function<Any, T>,
@ -47,7 +47,7 @@ class ConfigData<T : Any?> internal constructor(
override fun get(): T {
val cachedValue = value
if (cachedValue != null) return cachedValue //Speed things up
val config = config?.config
val config = config.config
val freshValue = config?.get(path) ?: primitiveDef.also { setInternal(it) }
return getter.apply(convertPrimitiveType(freshValue)).also { value = it }
}
@ -76,9 +76,13 @@ class ConfigData<T : Any?> internal constructor(
}
private fun setInternal(`val`: Any?) {
val conf = config ?: return
conf.config.set(path, `val`)
signalChange(conf)
val config = config.config
if (config != null) {
config.set(path, `val`)
signalChange(this.config)
} else {
ChromaUtils.logWarn("Attempted to get/set config value with no config! Path: $path, value: $`val`")
}
}
/**
@ -95,13 +99,9 @@ class ConfigData<T : Any?> internal constructor(
fun signalChange(config: IHaveConfig) {
val cc = config.config
val sa = config.saveAction
val root = cc.root
val root = cc?.root
if (root == null) {
if (MainPlugin.isInitialized) {
MainPlugin.instance.logger.warning("Attempted to save config with no root! Name: ${config.config.name}")
} else {
println("Attempted to save config with no root! Name: ${config.config.name}")
}
ChromaUtils.logWarn("Attempted to save config with no root! Name: ${cc?.name ?: "NONEXISTENT CONFIG"}")
return
}
if (!MainPlugin.isInitialized) {

View file

@ -15,12 +15,20 @@ class IHaveConfig(
/**
* The way the underlying configuration gets saved to disk
*/
val saveAction: Runnable,
saveAction: Runnable,
config: ConfigurationSection?
) {
/**
* The way the underlying configuration gets saved to disk
*/
var saveAction = saveAction
private set
/**
* Returns the Bukkit ConfigurationSection. Use [.signalChange] after changing it.
*/
var config: ConfigurationSection
) {
var config = config
private set
private val datamap = HashMap<String, IConfigData<*>>()
/**
@ -110,8 +118,9 @@ class IHaveConfig(
ConfigData.signalChange(this)
}
fun reload(section: ConfigurationSection) {
fun reload(section: ConfigurationSection, saveAction: Runnable = this.saveAction) {
config = section
this.saveAction = saveAction
datamap.forEach { it.value.reload() }
}

View file

@ -7,7 +7,7 @@ import java.util.function.Predicate
import java.util.function.UnaryOperator
class ListConfigData<T> internal constructor(
config: IHaveConfig?,
config: IHaveConfig,
path: String,
primitiveDef: ArrayList<*>,
private val elementGetter: Function<Any?, T>,
@ -27,9 +27,7 @@ class ListConfigData<T> internal constructor(
override val size: Int get() = primitiveList.size
private fun update() {
val config = listConfig.config
if (config != null) {
ConfigData.signalChange(config) //Update the config model and start save task if needed
}
ConfigData.signalChange(config) //Update the config model and start save task if needed
}
override fun set(index: Int, element: T): T {