mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-09-29 23:17:28 +02:00
Full System no longer required for database tests
- Extracted ErrorLogger interface - Created a new Component for Database tests - Added TestAPFModule and TestSystemObjectBindingModule - Database tests no longer enable the full system Affects issues: - Close #1401
This commit is contained in:
parent
c8f1585ee1
commit
6effdbec39
@ -37,7 +37,11 @@ public class BukkitCommand implements CommandExecutor, TabCompleter {
|
||||
private final ErrorLogger errorLogger;
|
||||
private final Subcommand command;
|
||||
|
||||
public BukkitCommand(RunnableFactory runnableFactory, ErrorLogger errorLogger, Subcommand command) {
|
||||
public BukkitCommand(
|
||||
RunnableFactory runnableFactory,
|
||||
ErrorLogger errorLogger,
|
||||
Subcommand command
|
||||
) {
|
||||
this.runnableFactory = runnableFactory;
|
||||
this.errorLogger = errorLogger;
|
||||
this.command = command;
|
||||
|
@ -41,8 +41,8 @@ public class BungeeCommand extends Command implements TabExecutor {
|
||||
|
||||
public BungeeCommand(
|
||||
RunnableFactory runnableFactory,
|
||||
ErrorLogger errorLogger, Subcommand command,
|
||||
String name
|
||||
ErrorLogger errorLogger,
|
||||
Subcommand command, String name
|
||||
) {
|
||||
super(name);
|
||||
this.runnableFactory = runnableFactory;
|
||||
|
@ -293,6 +293,13 @@ public class PlanSystem implements SubSystem {
|
||||
return extensionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Originally visible for testing purposes.
|
||||
*
|
||||
* @return the error logger of the system
|
||||
* @deprecated A smell, dagger should be used to construct things instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public ErrorLogger getErrorLogger() {
|
||||
return errorLogger;
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.LocaleSystem;
|
||||
import com.djrapitops.plan.storage.file.JarResource;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import com.djrapitops.plan.utilities.logging.PluginErrorLogger;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@ -70,4 +72,10 @@ public class SystemObjectProvidingModule {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ErrorLogger provideErrorLogger(PluginErrorLogger errorLogger) {
|
||||
return errorLogger;
|
||||
}
|
||||
|
||||
}
|
@ -81,4 +81,12 @@ public class ErrorContext implements Serializable {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ErrorContext{" +
|
||||
"related=" + related +
|
||||
", whatToDo='" + whatToDo + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -16,284 +16,22 @@
|
||||
*/
|
||||
package com.djrapitops.plan.utilities.logging;
|
||||
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.exceptions.ExceptionWithContext;
|
||||
import com.djrapitops.plan.identification.properties.ServerProperties;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.utilities.java.Lists;
|
||||
import com.djrapitops.plan.version.VersionChecker;
|
||||
import com.djrapitops.plugin.logging.L;
|
||||
import com.djrapitops.plugin.logging.console.PluginLogger;
|
||||
import com.djrapitops.plugin.logging.error.ErrorHandler;
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* New logger that logs errors to specific files.
|
||||
*
|
||||
* @author Rsl1122
|
||||
*/
|
||||
@Singleton
|
||||
public class ErrorLogger implements ErrorHandler {
|
||||
|
||||
private final PlanPlugin plugin;
|
||||
private final PluginLogger logger;
|
||||
private final PlanFiles files;
|
||||
private final Lazy<ServerProperties> serverProperties;
|
||||
private final Lazy<VersionChecker> versionChecker;
|
||||
private final Lazy<Formatters> formatters;
|
||||
|
||||
@Inject
|
||||
public ErrorLogger(
|
||||
PlanPlugin plugin,
|
||||
PluginLogger logger,
|
||||
PlanFiles files,
|
||||
Lazy<ServerProperties> serverProperties,
|
||||
Lazy<VersionChecker> versionChecker,
|
||||
Lazy<Formatters> formatters
|
||||
) {
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.files = files;
|
||||
this.serverProperties = serverProperties;
|
||||
this.versionChecker = versionChecker;
|
||||
this.formatters = formatters;
|
||||
}
|
||||
|
||||
public <T extends ExceptionWithContext> void log(L level, T throwable) {
|
||||
public interface ErrorLogger extends ErrorHandler {
|
||||
default <T extends ExceptionWithContext> void log(L level, T throwable) {
|
||||
log(level, (Throwable) throwable, throwable.getContext().orElse(ErrorContext.builder().related("Missing Context").build()));
|
||||
}
|
||||
|
||||
public void log(L level, Throwable throwable, ErrorContext context) {
|
||||
String errorName = throwable.getClass().getSimpleName();
|
||||
String hash = hash(throwable);
|
||||
Path logsDir = files.getLogsDirectory();
|
||||
Path errorLog = logsDir.resolve(errorName + "-" + hash + ".txt");
|
||||
mergeAdditionalContext(throwable, context);
|
||||
if (Files.exists(errorLog)) {
|
||||
logExisting(errorLog, throwable, context, hash);
|
||||
} else {
|
||||
logNew(errorLog, throwable, context, hash);
|
||||
}
|
||||
logToConsole(level, errorLog, throwable, context);
|
||||
if (L.CRITICAL == level) {
|
||||
plugin.getPluginLogger().error("CRITICAL error triggered a plugin shutdown.");
|
||||
plugin.onDisable();
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeAdditionalContext(Throwable throwable, ErrorContext context) {
|
||||
Throwable cause = throwable.getCause();
|
||||
while (cause != null) {
|
||||
if (cause instanceof ExceptionWithContext) {
|
||||
((ExceptionWithContext) cause).getContext().ifPresent(context::merge);
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private void logExisting(Path errorLog, Throwable throwable, ErrorContext context, String hash) {
|
||||
// Read existing
|
||||
List<String> lines;
|
||||
try (Stream<String> read = Files.lines(errorLog)) {
|
||||
lines = read.collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
logAfterReadError(errorLog, throwable, context, hash);
|
||||
return;
|
||||
}
|
||||
int occurrences = getOccurrences(lines) + 1;
|
||||
List<String> newLines = buildNewLines(context, lines, occurrences, hash);
|
||||
overwrite(errorLog, throwable, newLines);
|
||||
}
|
||||
|
||||
private void overwrite(Path errorLog, Throwable throwable, List<String> newLines) {
|
||||
try {
|
||||
Files.write(errorLog, newLines, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throwable.addSuppressed(e);
|
||||
Logger.getGlobal().log(Level.SEVERE, "Failed to log Plan error, see suppressed.", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> buildNewLines(ErrorContext context, List<String> lines, int occurrences, String hash) {
|
||||
Lists.Builder<String> builder = Lists.builder(String.class)
|
||||
.add(hash + " - Last occurred: " + getTimeStamp() + " Occurrences: " + occurrences);
|
||||
// 5 contexts are enough.
|
||||
if (occurrences <= 5) {
|
||||
builder = buildContext(context, occurrences, builder);
|
||||
}
|
||||
int lineCount = lines.size();
|
||||
int firstContextLineIndex = findFirstContextLine(lines, lineCount);
|
||||
return builder.addAll(lines.subList(firstContextLineIndex, lineCount))
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getTimeStamp() {
|
||||
return formatters.get().iso8601NoClockLong().apply(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private Lists.Builder<String> buildContext(ErrorContext context, int occurrences, Lists.Builder<String> builder) {
|
||||
return builder.add("---- Context " + occurrences + " ----")
|
||||
.add("Plan v" + versionChecker.get().getCurrentVersion())
|
||||
.add(serverProperties.get().getName() + " " + serverProperties.get().getVersion())
|
||||
.add("Server v" + serverProperties.get().getImplVersion())
|
||||
.add("")
|
||||
.addAll(context.toLines())
|
||||
.add("");
|
||||
}
|
||||
|
||||
private void logAfterReadError(Path errorLog, Throwable throwable, ErrorContext context, String hash) {
|
||||
logger.error("Failed to read " + errorLog + " deleting file");
|
||||
try {
|
||||
Files.deleteIfExists(errorLog);
|
||||
} catch (IOException ioException) {
|
||||
logger.error("Failed to delete " + errorLog);
|
||||
}
|
||||
logNew(errorLog, throwable, context, hash);
|
||||
}
|
||||
|
||||
private int getOccurrences(List<String> lines) {
|
||||
String occurLine = lines.get(0);
|
||||
return Integer.parseInt(StringUtils.splitByWholeSeparator(occurLine, ": ")[2].trim());
|
||||
}
|
||||
|
||||
private int findFirstContextLine(List<String> lines, int lineCount) {
|
||||
int firstContextLineIndex = 0;
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
if (lines.get(i).contains("---- Context")) {
|
||||
firstContextLineIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return firstContextLineIndex;
|
||||
}
|
||||
|
||||
private void logToConsole(L level, Path errorLog, Throwable throwable, ErrorContext context) {
|
||||
String errorName = throwable.getClass().getSimpleName();
|
||||
String errorMsg = throwable.getMessage();
|
||||
String errorLocation = errorLog.toString();
|
||||
logger.log(level,
|
||||
"Ran into " + errorName + " - logged to " + errorLocation,
|
||||
"(INCLUDE CONTENTS OF THE FILE IN ANY REPORTS)",
|
||||
context.getWhatToDo().map(td -> "What to do: " + td).orElse("Error msg: \"" + errorMsg + "\"")
|
||||
);
|
||||
}
|
||||
|
||||
private void logNew(Path errorLog, Throwable throwable, ErrorContext context, String hash) {
|
||||
List<String> stacktrace = buildReadableStacktrace(new ArrayList<>(), throwable);
|
||||
List<String> lines = Lists.builder(String.class)
|
||||
.add(hash + " - Last occurred: " + getTimeStamp() + " Occurrences: 1")
|
||||
.apply(builder -> this.buildContext(context, 1, builder))
|
||||
.add("---- Stacktrace ----")
|
||||
.addAll(stacktrace)
|
||||
.build();
|
||||
writeNew(errorLog, throwable, lines);
|
||||
}
|
||||
|
||||
private void writeNew(Path errorLog, Throwable throwable, List<String> lines) {
|
||||
try {
|
||||
Files.createDirectories(errorLog.getParent());
|
||||
Files.write(errorLog, lines, StandardOpenOption.CREATE_NEW, StandardOpenOption.APPEND);
|
||||
} catch (IOException e) {
|
||||
throwable.addSuppressed(e);
|
||||
Logger.getGlobal().log(Level.SEVERE, "Failed to log Plan error, see suppressed.", throwable);
|
||||
}
|
||||
}
|
||||
void log(L level, Throwable throwable, ErrorContext context);
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void log(L level, Class caughtBy, Throwable throwable) {
|
||||
default void log(L level, Class caughtBy, Throwable throwable) {
|
||||
log(level, throwable, ErrorContext.builder()
|
||||
.related("Caught by " + caughtBy.getName())
|
||||
.build());
|
||||
}
|
||||
|
||||
private String hash(Throwable e) {
|
||||
int seed = 0;
|
||||
Throwable cause = e;
|
||||
String previousLine = null;
|
||||
while (cause != null) {
|
||||
for (StackTraceElement element : cause.getStackTrace()) {
|
||||
String asLine = element.toString();
|
||||
if (asLine.equals(previousLine)) continue;
|
||||
if (seed == 0) {
|
||||
seed = asLine.hashCode();
|
||||
} else {
|
||||
seed *= asLine.hashCode();
|
||||
}
|
||||
previousLine = asLine;
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return DigestUtils.sha256Hex(Integer.toString(seed)).substring(0, 10);
|
||||
}
|
||||
|
||||
private List<String> buildReadableStacktrace(List<String> trace, Throwable e) {
|
||||
trace.add(e.toString());
|
||||
Deduplicator deduplicator = new Deduplicator();
|
||||
for (StackTraceElement element : e.getStackTrace()) {
|
||||
String line = element.toString();
|
||||
deduplicator.addLines(trace, line);
|
||||
}
|
||||
deduplicator.addLeftoverDuplicateCountLine(trace);
|
||||
Throwable[] suppressed = e.getSuppressed();
|
||||
if (suppressed.length > 0) {
|
||||
for (Throwable suppressedThrowable : suppressed) {
|
||||
trace.add(" Suppressed:");
|
||||
for (String line : buildReadableStacktrace(new ArrayList<>(), suppressedThrowable)) {
|
||||
trace.add(" " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null) {
|
||||
trace.add("Caused by:");
|
||||
buildReadableStacktrace(trace, cause);
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
|
||||
private static class Deduplicator {
|
||||
private String previousLine = null;
|
||||
private String lastDuplicate = null;
|
||||
private int duplicateCount = 0;
|
||||
|
||||
public void addLines(List<String> trace, String line) {
|
||||
if (duplicateCount > 0 && !line.equals(lastDuplicate)) {
|
||||
String returnLine = " x " + duplicateCount;
|
||||
duplicateCount = 1;
|
||||
trace.add(returnLine);
|
||||
trace.add(" " + line);
|
||||
} else if (line.equals(lastDuplicate)) {
|
||||
duplicateCount++;
|
||||
} else if (line.equals(previousLine)) {
|
||||
lastDuplicate = line;
|
||||
duplicateCount = 2;
|
||||
} else {
|
||||
previousLine = line;
|
||||
trace.add(" " + line);
|
||||
}
|
||||
}
|
||||
|
||||
public void addLeftoverDuplicateCountLine(List<String> trace) {
|
||||
if (duplicateCount > 0) {
|
||||
trace.add(" x " + duplicateCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,287 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.utilities.logging;
|
||||
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.exceptions.ExceptionWithContext;
|
||||
import com.djrapitops.plan.identification.properties.ServerProperties;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.utilities.java.Lists;
|
||||
import com.djrapitops.plan.version.VersionChecker;
|
||||
import com.djrapitops.plugin.logging.L;
|
||||
import com.djrapitops.plugin.logging.console.PluginLogger;
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* New logger that logs errors to specific files.
|
||||
*
|
||||
* @author Rsl1122
|
||||
*/
|
||||
@Singleton
|
||||
public class PluginErrorLogger implements ErrorLogger {
|
||||
|
||||
private final PlanPlugin plugin;
|
||||
private final PluginLogger logger;
|
||||
private final PlanFiles files;
|
||||
private final Lazy<ServerProperties> serverProperties;
|
||||
private final Lazy<VersionChecker> versionChecker;
|
||||
private final Lazy<Formatters> formatters;
|
||||
|
||||
@Inject
|
||||
public PluginErrorLogger(
|
||||
PlanPlugin plugin,
|
||||
PluginLogger logger,
|
||||
PlanFiles files,
|
||||
Lazy<ServerProperties> serverProperties,
|
||||
Lazy<VersionChecker> versionChecker,
|
||||
Lazy<Formatters> formatters
|
||||
) {
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.files = files;
|
||||
this.serverProperties = serverProperties;
|
||||
this.versionChecker = versionChecker;
|
||||
this.formatters = formatters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(L level, Throwable throwable, ErrorContext context) {
|
||||
String errorName = throwable.getClass().getSimpleName();
|
||||
String hash = hash(throwable);
|
||||
Path logsDir = files.getLogsDirectory();
|
||||
Path errorLog = logsDir.resolve(errorName + "-" + hash + ".txt");
|
||||
mergeAdditionalContext(throwable, context);
|
||||
if (Files.exists(errorLog)) {
|
||||
logExisting(errorLog, throwable, context, hash);
|
||||
} else {
|
||||
logNew(errorLog, throwable, context, hash);
|
||||
}
|
||||
logToConsole(level, errorLog, throwable, context);
|
||||
if (L.CRITICAL == level) {
|
||||
plugin.getPluginLogger().error("CRITICAL error triggered a plugin shutdown.");
|
||||
plugin.onDisable();
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeAdditionalContext(Throwable throwable, ErrorContext context) {
|
||||
Throwable cause = throwable.getCause();
|
||||
while (cause != null) {
|
||||
if (cause instanceof ExceptionWithContext) {
|
||||
((ExceptionWithContext) cause).getContext().ifPresent(context::merge);
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private void logExisting(Path errorLog, Throwable throwable, ErrorContext context, String hash) {
|
||||
// Read existing
|
||||
List<String> lines;
|
||||
try (Stream<String> read = Files.lines(errorLog)) {
|
||||
lines = read.collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
logAfterReadError(errorLog, throwable, context, hash);
|
||||
return;
|
||||
}
|
||||
int occurrences = getOccurrences(lines) + 1;
|
||||
List<String> newLines = buildNewLines(context, lines, occurrences, hash);
|
||||
overwrite(errorLog, throwable, newLines);
|
||||
}
|
||||
|
||||
private void overwrite(Path errorLog, Throwable throwable, List<String> newLines) {
|
||||
try {
|
||||
Files.write(errorLog, newLines, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throwable.addSuppressed(e);
|
||||
Logger.getGlobal().log(Level.SEVERE, "Failed to log Plan error, see suppressed.", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> buildNewLines(ErrorContext context, List<String> lines, int occurrences, String hash) {
|
||||
Lists.Builder<String> builder = Lists.builder(String.class)
|
||||
.add(hash + " - Last occurred: " + getTimeStamp() + " Occurrences: " + occurrences);
|
||||
// 5 contexts are enough.
|
||||
if (occurrences <= 5) {
|
||||
builder = buildContext(context, occurrences, builder);
|
||||
}
|
||||
int lineCount = lines.size();
|
||||
int firstContextLineIndex = findFirstContextLine(lines, lineCount);
|
||||
return builder.addAll(lines.subList(firstContextLineIndex, lineCount))
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getTimeStamp() {
|
||||
return formatters.get().iso8601NoClockLong().apply(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private Lists.Builder<String> buildContext(ErrorContext context, int occurrences, Lists.Builder<String> builder) {
|
||||
return builder.add("---- Context " + occurrences + " ----")
|
||||
.add("Plan v" + versionChecker.get().getCurrentVersion())
|
||||
.add(serverProperties.get().getName() + " " + serverProperties.get().getVersion())
|
||||
.add("Server v" + serverProperties.get().getImplVersion())
|
||||
.add("")
|
||||
.addAll(context.toLines())
|
||||
.add("");
|
||||
}
|
||||
|
||||
private void logAfterReadError(Path errorLog, Throwable throwable, ErrorContext context, String hash) {
|
||||
logger.error("Failed to read " + errorLog + " deleting file");
|
||||
try {
|
||||
Files.deleteIfExists(errorLog);
|
||||
} catch (IOException ioException) {
|
||||
logger.error("Failed to delete " + errorLog);
|
||||
}
|
||||
logNew(errorLog, throwable, context, hash);
|
||||
}
|
||||
|
||||
private int getOccurrences(List<String> lines) {
|
||||
String occurLine = lines.get(0);
|
||||
return Integer.parseInt(StringUtils.splitByWholeSeparator(occurLine, ": ")[2].trim());
|
||||
}
|
||||
|
||||
private int findFirstContextLine(List<String> lines, int lineCount) {
|
||||
int firstContextLineIndex = 0;
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
if (lines.get(i).contains("---- Context")) {
|
||||
firstContextLineIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return firstContextLineIndex;
|
||||
}
|
||||
|
||||
private void logToConsole(L level, Path errorLog, Throwable throwable, ErrorContext context) {
|
||||
String errorName = throwable.getClass().getSimpleName();
|
||||
String errorMsg = throwable.getMessage();
|
||||
String errorLocation = errorLog.toString();
|
||||
logger.log(level,
|
||||
"Ran into " + errorName + " - logged to " + errorLocation,
|
||||
"(INCLUDE CONTENTS OF THE FILE IN ANY REPORTS)",
|
||||
context.getWhatToDo().map(td -> "What to do: " + td).orElse("Error msg: \"" + errorMsg + "\"")
|
||||
);
|
||||
}
|
||||
|
||||
private void logNew(Path errorLog, Throwable throwable, ErrorContext context, String hash) {
|
||||
List<String> stacktrace = buildReadableStacktrace(new ArrayList<>(), throwable);
|
||||
List<String> lines = Lists.builder(String.class)
|
||||
.add(hash + " - Last occurred: " + getTimeStamp() + " Occurrences: 1")
|
||||
.apply(builder -> this.buildContext(context, 1, builder))
|
||||
.add("---- Stacktrace ----")
|
||||
.addAll(stacktrace)
|
||||
.build();
|
||||
writeNew(errorLog, throwable, lines);
|
||||
}
|
||||
|
||||
private void writeNew(Path errorLog, Throwable throwable, List<String> lines) {
|
||||
try {
|
||||
Files.createDirectories(errorLog.getParent());
|
||||
Files.write(errorLog, lines, StandardOpenOption.CREATE_NEW, StandardOpenOption.APPEND);
|
||||
} catch (IOException e) {
|
||||
throwable.addSuppressed(e);
|
||||
Logger.getGlobal().log(Level.SEVERE, "Failed to log Plan error, see suppressed.", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private String hash(Throwable e) {
|
||||
int seed = 0;
|
||||
Throwable cause = e;
|
||||
String previousLine = null;
|
||||
while (cause != null) {
|
||||
for (StackTraceElement element : cause.getStackTrace()) {
|
||||
String asLine = element.toString();
|
||||
if (asLine.equals(previousLine)) continue;
|
||||
if (seed == 0) {
|
||||
seed = asLine.hashCode();
|
||||
} else {
|
||||
seed *= asLine.hashCode();
|
||||
}
|
||||
previousLine = asLine;
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return DigestUtils.sha256Hex(Integer.toString(seed)).substring(0, 10);
|
||||
}
|
||||
|
||||
private List<String> buildReadableStacktrace(List<String> trace, Throwable e) {
|
||||
trace.add(e.toString());
|
||||
Deduplicator deduplicator = new Deduplicator();
|
||||
for (StackTraceElement element : e.getStackTrace()) {
|
||||
String line = element.toString();
|
||||
deduplicator.addLines(trace, line);
|
||||
}
|
||||
deduplicator.addLeftoverDuplicateCountLine(trace);
|
||||
Throwable[] suppressed = e.getSuppressed();
|
||||
if (suppressed.length > 0) {
|
||||
for (Throwable suppressedThrowable : suppressed) {
|
||||
trace.add(" Suppressed:");
|
||||
for (String line : buildReadableStacktrace(new ArrayList<>(), suppressedThrowable)) {
|
||||
trace.add(" " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null) {
|
||||
trace.add("Caused by:");
|
||||
buildReadableStacktrace(trace, cause);
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
|
||||
private static class Deduplicator {
|
||||
private String previousLine = null;
|
||||
private String lastDuplicate = null;
|
||||
private int duplicateCount = 0;
|
||||
|
||||
public void addLines(List<String> trace, String line) {
|
||||
if (duplicateCount > 0 && !line.equals(lastDuplicate)) {
|
||||
String returnLine = " x " + duplicateCount;
|
||||
duplicateCount = 1;
|
||||
trace.add(returnLine);
|
||||
trace.add(" " + line);
|
||||
} else if (line.equals(lastDuplicate)) {
|
||||
duplicateCount++;
|
||||
} else if (line.equals(previousLine)) {
|
||||
lastDuplicate = line;
|
||||
duplicateCount = 2;
|
||||
} else {
|
||||
previousLine = line;
|
||||
trace.add(" " + line);
|
||||
}
|
||||
}
|
||||
|
||||
public void addLeftoverDuplicateCountLine(List<String> trace) {
|
||||
if (duplicateCount > 0) {
|
||||
trace.add(" x " + duplicateCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
package com.djrapitops.plan.version;
|
||||
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.SubSystem;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
@ -29,6 +28,7 @@ import com.djrapitops.plugin.api.utility.Version;
|
||||
import com.djrapitops.plugin.logging.L;
|
||||
import com.djrapitops.plugin.logging.console.PluginLogger;
|
||||
import com.djrapitops.plugin.task.AbsRunnable;
|
||||
import com.djrapitops.plugin.task.RunnableFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@ -49,8 +49,8 @@ public class VersionChecker implements SubSystem {
|
||||
private final Locale locale;
|
||||
private final PlanConfig config;
|
||||
private final PluginLogger logger;
|
||||
private final RunnableFactory runnableFactory;
|
||||
private final ErrorLogger errorLogger;
|
||||
private final PlanPlugin plugin;
|
||||
|
||||
private VersionInfo newVersionAvailable;
|
||||
|
||||
@ -60,15 +60,15 @@ public class VersionChecker implements SubSystem {
|
||||
Locale locale,
|
||||
PlanConfig config,
|
||||
PluginLogger logger,
|
||||
ErrorLogger errorLogger,
|
||||
PlanPlugin plugin
|
||||
RunnableFactory runnableFactory,
|
||||
ErrorLogger errorLogger
|
||||
) {
|
||||
this.currentVersion = currentVersion;
|
||||
this.locale = locale;
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
this.runnableFactory = runnableFactory;
|
||||
this.errorLogger = errorLogger;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public boolean isNewVersionAvailable() {
|
||||
@ -108,7 +108,7 @@ public class VersionChecker implements SubSystem {
|
||||
if (config.isFalse(PluginSettings.CHECK_FOR_UPDATES)) {
|
||||
return;
|
||||
}
|
||||
plugin.getRunnableFactory().create("VersionChecker", new AbsRunnable() {
|
||||
runnableFactory.create("VersionChecker", new AbsRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
checkForUpdates();
|
||||
|
@ -66,12 +66,14 @@ class ShutdownSaveTest {
|
||||
|
||||
@BeforeEach
|
||||
void setupShutdownSaveObject(@TempDir Path temporaryFolder) throws Exception {
|
||||
PlanPluginComponent pluginComponent = DaggerPlanPluginComponent.builder().plan(
|
||||
PlanPluginMocker.setUp()
|
||||
.withDataFolder(temporaryFolder.resolve("ShutdownSaveTest").toFile())
|
||||
.withLogging()
|
||||
.getPlanMock()
|
||||
).build();
|
||||
PlanPluginComponent pluginComponent = DaggerPlanPluginComponent.builder()
|
||||
.bindTemporaryDirectory(temporaryFolder)
|
||||
.plan(
|
||||
PlanPluginMocker.setUp()
|
||||
.withDataFolder(temporaryFolder.resolve("ShutdownSaveTest").toFile())
|
||||
.withLogging()
|
||||
.getPlanMock()
|
||||
).build();
|
||||
PlanSystem system = pluginComponent.system();
|
||||
|
||||
database = system.getDatabaseSystem().getSqLiteFactory().usingFileCalled("test");
|
||||
|
@ -131,11 +131,11 @@ public interface DatabaseTest extends DatabaseTestPreparer {
|
||||
|
||||
TestPluginLogger logger = new TestPluginLogger();
|
||||
new DBCleanTask(
|
||||
system().getConfigSystem().getConfig(),
|
||||
config(),
|
||||
new Locale(),
|
||||
system().getDatabaseSystem(),
|
||||
new QuerySvc(system().getDatabaseSystem(), system().getServerInfo(), null),
|
||||
system().getServerInfo(),
|
||||
dbSystem(),
|
||||
new QuerySvc(dbSystem(), serverInfo(), null),
|
||||
serverInfo(),
|
||||
logger,
|
||||
null
|
||||
).cleanOldPlayers(db());
|
||||
@ -173,7 +173,7 @@ public interface DatabaseTest extends DatabaseTestPreparer {
|
||||
|
||||
@Test
|
||||
default void configIsStoredInTheDatabase() {
|
||||
PlanConfig config = system().getConfigSystem().getConfig();
|
||||
PlanConfig config = config();
|
||||
|
||||
db().executeTransaction(new StoreConfigTransaction(serverUUID(), config, System.currentTimeMillis()));
|
||||
|
||||
@ -187,7 +187,7 @@ public interface DatabaseTest extends DatabaseTestPreparer {
|
||||
configIsStoredInTheDatabase();
|
||||
long savedMs = System.currentTimeMillis();
|
||||
|
||||
PlanConfig config = system().getConfigSystem().getConfig();
|
||||
PlanConfig config = config();
|
||||
|
||||
db().executeTransaction(new StoreConfigTransaction(serverUUID(), config, System.currentTimeMillis()));
|
||||
|
||||
@ -256,7 +256,7 @@ public interface DatabaseTest extends DatabaseTestPreparer {
|
||||
String testSQL = SELECT + sql.dateToDayStamp(sql.epochSecondToDate(Long.toString((time + offset) / 1000))) + " as date";
|
||||
|
||||
System.out.println(testSQL);
|
||||
String expected = system().getDeliveryUtilities().getFormatters().iso8601NoClockLong().apply(time);
|
||||
String expected = deliveryUtilities().getFormatters().iso8601NoClockLong().apply(time);
|
||||
String result = db.query(new QueryAllStatement<String>(testSQL) {
|
||||
@Override
|
||||
public String processResults(ResultSet set) throws SQLException {
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database;
|
||||
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.ConfigSystem;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Component;
|
||||
import utilities.DBPreparer;
|
||||
import utilities.dagger.*;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
DBSystemModule.class,
|
||||
TestSystemObjectProvidingModule.class,
|
||||
|
||||
TestAPFModule.class,
|
||||
PlanPluginModule.class,
|
||||
PluginServerPropertiesModule.class,
|
||||
PluginSuperClassBindingModule.class
|
||||
})
|
||||
public interface DatabaseTestComponent extends DBPreparer.Dependencies {
|
||||
|
||||
default void enable() {
|
||||
configSystem().enable();
|
||||
dbSystem().enable();
|
||||
serverInfo().enable();
|
||||
}
|
||||
|
||||
default void disable() {
|
||||
serverInfo().disable();
|
||||
dbSystem().disable();
|
||||
configSystem().disable();
|
||||
}
|
||||
|
||||
PlanConfig config();
|
||||
|
||||
ConfigSystem configSystem();
|
||||
|
||||
DBSystem dbSystem();
|
||||
|
||||
ServerInfo serverInfo();
|
||||
|
||||
DeliveryUtilities deliveryUtilities();
|
||||
|
||||
PlanFiles files();
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
Builder bindTemporaryDirectory(@Named("tempDir") Path tempDir);
|
||||
|
||||
DatabaseTestComponent build();
|
||||
}
|
||||
|
||||
}
|
@ -17,6 +17,9 @@
|
||||
package com.djrapitops.plan.storage.database;
|
||||
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.storage.database.transactions.Executable;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import utilities.TestConstants;
|
||||
@ -33,8 +36,25 @@ public interface DatabaseTestPreparer {
|
||||
|
||||
UUID serverUUID();
|
||||
|
||||
@Deprecated
|
||||
PlanSystem system();
|
||||
|
||||
default PlanConfig config() {
|
||||
return system().getConfigSystem().getConfig();
|
||||
}
|
||||
|
||||
default DBSystem dbSystem() {
|
||||
return system().getDatabaseSystem();
|
||||
}
|
||||
|
||||
default ServerInfo serverInfo() {
|
||||
return system().getServerInfo();
|
||||
}
|
||||
|
||||
default DeliveryUtilities deliveryUtilities() {
|
||||
return system().getDeliveryUtilities();
|
||||
}
|
||||
|
||||
default void execute(Executable executable) {
|
||||
db().executeTransaction(new Transaction() {
|
||||
@Override
|
||||
|
@ -17,7 +17,10 @@
|
||||
package com.djrapitops.plan.storage.database;
|
||||
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.storage.database.queries.*;
|
||||
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
|
||||
@ -28,15 +31,16 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import utilities.DBPreparer;
|
||||
import utilities.RandomData;
|
||||
import utilities.mocks.PluginMockComponent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for the H2 database.
|
||||
@ -61,13 +65,16 @@ public class H2Test implements DatabaseTest,
|
||||
|
||||
private static final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500);
|
||||
|
||||
private static PlanSystem system;
|
||||
private static Database database;
|
||||
private static DatabaseTestComponent component;
|
||||
|
||||
@BeforeAll
|
||||
static void setupDatabase(@TempDir Path temp) throws Exception {
|
||||
system = new PluginMockComponent(temp).getPlanSystem();
|
||||
database = new DBPreparer(system, TEST_PORT_NUMBER).prepareH2()
|
||||
component = DaggerDatabaseTestComponent.builder()
|
||||
.bindTemporaryDirectory(temp)
|
||||
.build();
|
||||
component.enable();
|
||||
database = new DBPreparer(component, TEST_PORT_NUMBER).prepareH2()
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
}
|
||||
|
||||
@ -101,7 +108,7 @@ public class H2Test implements DatabaseTest,
|
||||
database.close();
|
||||
System.out.println("Database state after close: " + database.getState().name());
|
||||
}
|
||||
system.disable();
|
||||
component.disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -111,11 +118,33 @@ public class H2Test implements DatabaseTest,
|
||||
|
||||
@Override
|
||||
public UUID serverUUID() {
|
||||
return system.getServerInfo().getServerUUID();
|
||||
return component.serverInfo().getServerUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanConfig config() {
|
||||
return component.config();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBSystem dbSystem() {
|
||||
return component.dbSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo serverInfo() {
|
||||
return component.serverInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeliveryUtilities deliveryUtilities() {
|
||||
return component.deliveryUtilities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanSystem system() {
|
||||
return system;
|
||||
PlanSystem mockSystem = Mockito.mock(PlanSystem.class);
|
||||
when(mockSystem.getPlanFiles()).thenReturn(component.files());
|
||||
return mockSystem;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,10 @@
|
||||
package com.djrapitops.plan.storage.database;
|
||||
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.storage.database.queries.*;
|
||||
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
|
||||
@ -29,16 +32,17 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import utilities.DBPreparer;
|
||||
import utilities.RandomData;
|
||||
import utilities.mocks.PluginMockComponent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for MySQL database.
|
||||
@ -67,13 +71,16 @@ class MySQLTest implements DatabaseTest,
|
||||
|
||||
private static final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500);
|
||||
|
||||
private static PlanSystem system;
|
||||
private static Database database;
|
||||
private static DatabaseTestComponent component;
|
||||
|
||||
@BeforeAll
|
||||
static void setupDatabase(@TempDir Path temp) throws Exception {
|
||||
system = new PluginMockComponent(temp).getPlanSystem();
|
||||
Optional<Database> mysql = new DBPreparer(system, TEST_PORT_NUMBER).prepareMySQL();
|
||||
component = DaggerDatabaseTestComponent.builder()
|
||||
.bindTemporaryDirectory(temp)
|
||||
.build();
|
||||
component.enable();
|
||||
Optional<Database> mysql = new DBPreparer(component, TEST_PORT_NUMBER).prepareMySQL();
|
||||
Assumptions.assumeTrue(mysql.isPresent());
|
||||
database = mysql.get();
|
||||
}
|
||||
@ -104,7 +111,7 @@ class MySQLTest implements DatabaseTest,
|
||||
@AfterAll
|
||||
static void disableSystem() {
|
||||
if (database != null) database.close();
|
||||
system.disable();
|
||||
component.disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -114,11 +121,33 @@ class MySQLTest implements DatabaseTest,
|
||||
|
||||
@Override
|
||||
public UUID serverUUID() {
|
||||
return system.getServerInfo().getServerUUID();
|
||||
return component.serverInfo().getServerUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanConfig config() {
|
||||
return component.config();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBSystem dbSystem() {
|
||||
return component.dbSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo serverInfo() {
|
||||
return component.serverInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeliveryUtilities deliveryUtilities() {
|
||||
return component.deliveryUtilities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanSystem system() {
|
||||
return system;
|
||||
PlanSystem mockSystem = Mockito.mock(PlanSystem.class);
|
||||
when(mockSystem.getPlanFiles()).thenReturn(component.files());
|
||||
return mockSystem;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,10 @@
|
||||
package com.djrapitops.plan.storage.database;
|
||||
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.storage.database.queries.*;
|
||||
import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
|
||||
@ -28,15 +31,16 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import utilities.DBPreparer;
|
||||
import utilities.RandomData;
|
||||
import utilities.mocks.PluginMockComponent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for SQLite Database.
|
||||
@ -61,13 +65,16 @@ public class SQLiteTest implements DatabaseTest,
|
||||
|
||||
private static final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500);
|
||||
|
||||
private static PlanSystem system;
|
||||
private static Database database;
|
||||
private static DatabaseTestComponent component;
|
||||
|
||||
@BeforeAll
|
||||
static void setupDatabase(@TempDir Path temp) throws Exception {
|
||||
system = new PluginMockComponent(temp).getPlanSystem();
|
||||
database = new DBPreparer(system, TEST_PORT_NUMBER).prepareSQLite()
|
||||
component = DaggerDatabaseTestComponent.builder()
|
||||
.bindTemporaryDirectory(temp)
|
||||
.build();
|
||||
component.enable();
|
||||
database = new DBPreparer(component, TEST_PORT_NUMBER).prepareSQLite()
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
}
|
||||
|
||||
@ -98,7 +105,7 @@ public class SQLiteTest implements DatabaseTest,
|
||||
@AfterAll
|
||||
static void disableSystem() {
|
||||
if (database != null) database.close();
|
||||
system.disable();
|
||||
component.disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,11 +115,33 @@ public class SQLiteTest implements DatabaseTest,
|
||||
|
||||
@Override
|
||||
public UUID serverUUID() {
|
||||
return system.getServerInfo().getServerUUID();
|
||||
return component.serverInfo().getServerUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanConfig config() {
|
||||
return component.config();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBSystem dbSystem() {
|
||||
return component.dbSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo serverInfo() {
|
||||
return component.serverInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeliveryUtilities deliveryUtilities() {
|
||||
return component.deliveryUtilities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanSystem system() {
|
||||
return system;
|
||||
PlanSystem mockSystem = Mockito.mock(PlanSystem.class);
|
||||
when(mockSystem.getPlanFiles()).thenReturn(component.files());
|
||||
return mockSystem;
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ public interface DatabaseBackupTest extends DatabaseTestPreparer {
|
||||
default void testBackupAndRestoreSQLite() throws Exception {
|
||||
File tempFile = Files.createTempFile(system().getPlanFiles().getDataFolder().toPath(), "backup-", ".db").toFile();
|
||||
tempFile.deleteOnExit();
|
||||
SQLiteDB backup = system().getDatabaseSystem().getSqLiteFactory().usingFile(tempFile);
|
||||
SQLiteDB backup = dbSystem().getSqLiteFactory().usingFile(tempFile);
|
||||
backup.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
|
||||
try {
|
||||
backup.init();
|
||||
@ -103,7 +103,7 @@ public interface DatabaseBackupTest extends DatabaseTestPreparer {
|
||||
default void testBackupAndRestoreH2() throws Exception {
|
||||
File tempFile = Files.createTempFile(system().getPlanFiles().getDataFolder().toPath(), "backup-", ".db").toFile();
|
||||
tempFile.deleteOnExit();
|
||||
H2DB backup = system().getDatabaseSystem().getH2Factory().usingFile(tempFile);
|
||||
H2DB backup = dbSystem().getH2Factory().usingFile(tempFile);
|
||||
backup.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
|
||||
try {
|
||||
backup.init();
|
||||
|
@ -49,7 +49,7 @@ class ErrorLoggerTest {
|
||||
PluginMockComponent component = new PluginMockComponent(dir);
|
||||
SYSTEM = component.getPlanSystem();
|
||||
SYSTEM.enable();
|
||||
UNDER_TEST = SYSTEM.getErrorLogger();
|
||||
UNDER_TEST = component.getPluginErrorLogger();
|
||||
LOGS_DIR = SYSTEM.getPlanFiles().getLogsDirectory();
|
||||
}
|
||||
|
||||
|
@ -34,11 +34,15 @@ import java.util.Optional;
|
||||
|
||||
public class DBPreparer {
|
||||
|
||||
private final PlanSystem system;
|
||||
private final Dependencies dependencies;
|
||||
private final int testPortNumber;
|
||||
|
||||
public DBPreparer(PlanSystem system, int testPortNumber) {
|
||||
this.system = system;
|
||||
this(new PlanSystemAsDependencies(system), testPortNumber);
|
||||
}
|
||||
|
||||
public DBPreparer(Dependencies dependencies, int testPortNumber) {
|
||||
this.dependencies = dependencies;
|
||||
this.testPortNumber = testPortNumber;
|
||||
}
|
||||
|
||||
@ -53,12 +57,12 @@ public class DBPreparer {
|
||||
}
|
||||
|
||||
private SQLDB prepareDBByName(String dbName) throws EnableException {
|
||||
PlanConfig config = system.getConfigSystem().getConfig();
|
||||
PlanConfig config = dependencies.config();
|
||||
config.set(WebserverSettings.PORT, testPortNumber);
|
||||
config.set(DatabaseSettings.TYPE, dbName);
|
||||
system.enable();
|
||||
|
||||
DBSystem dbSystem = system.getDatabaseSystem();
|
||||
DBSystem dbSystem = dependencies.dbSystem();
|
||||
dbSystem.enable();
|
||||
SQLDB db = (SQLDB) dbSystem.getActiveDatabaseByName(dbName);
|
||||
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
|
||||
db.init();
|
||||
@ -91,7 +95,7 @@ public class DBPreparer {
|
||||
}
|
||||
|
||||
public Optional<Database> prepareMySQL() throws EnableException {
|
||||
PlanConfig config = system.getConfigSystem().getConfig();
|
||||
PlanConfig config = dependencies.config();
|
||||
Optional<String> formattedDB = setUpMySQLSettings(config);
|
||||
if (formattedDB.isPresent()) {
|
||||
String formattedDatabase = formattedDB.get();
|
||||
@ -108,4 +112,29 @@ public class DBPreparer {
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public interface Dependencies {
|
||||
PlanConfig config();
|
||||
|
||||
DBSystem dbSystem();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
static class PlanSystemAsDependencies implements Dependencies {
|
||||
private final PlanSystem system;
|
||||
|
||||
PlanSystemAsDependencies(PlanSystem system) {
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanConfig config() {
|
||||
return system.getConfigSystem().getConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBSystem dbSystem() {
|
||||
return system.getDatabaseSystem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package utilities.dagger;
|
||||
|
||||
import com.djrapitops.plan.exceptions.EnableException;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DatabaseSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.H2DB;
|
||||
import com.djrapitops.plan.storage.database.MySQLDB;
|
||||
import com.djrapitops.plan.storage.database.SQLiteDB;
|
||||
import com.djrapitops.plugin.logging.console.PluginLogger;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module
|
||||
public class DBSystemModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
DBSystem provideDatabaseSystem(
|
||||
PlanConfig config,
|
||||
Locale locale,
|
||||
SQLiteDB.Factory sqLiteDB,
|
||||
H2DB.Factory h2Factory,
|
||||
MySQLDB mySQLDB,
|
||||
PluginLogger logger
|
||||
) {
|
||||
return new DBSystem(locale, sqLiteDB, h2Factory, logger) {
|
||||
@Override
|
||||
public void enable() throws EnableException {
|
||||
databases.add(sqLiteDB.usingDefaultFile());
|
||||
databases.add(h2Factory.usingDefaultFile());
|
||||
databases.add(mySQLDB);
|
||||
String dbType = config.get(DatabaseSettings.TYPE).toLowerCase().trim();
|
||||
db = getActiveDatabaseByName(dbType);
|
||||
super.enable();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -19,13 +19,14 @@ package utilities.dagger;
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.commands.PlanCommand;
|
||||
import com.djrapitops.plan.modules.APFModule;
|
||||
import com.djrapitops.plan.modules.PlaceholderModule;
|
||||
import com.djrapitops.plan.modules.SystemObjectProvidingModule;
|
||||
import com.djrapitops.plan.utilities.logging.PluginErrorLogger;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Component;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Dagger component for {@link com.djrapitops.plan.PlanPlugin} based Plan system.
|
||||
@ -35,20 +36,25 @@ import javax.inject.Singleton;
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
PlanPluginModule.class,
|
||||
SystemObjectProvidingModule.class,
|
||||
APFModule.class,
|
||||
TestSystemObjectProvidingModule.class,
|
||||
TestAPFModule.class,
|
||||
PlaceholderModule.class,
|
||||
|
||||
PluginServerPropertiesModule.class,
|
||||
PluginSuperClassBindingModule.class
|
||||
PluginSuperClassBindingModule.class,
|
||||
DBSystemModule.class
|
||||
})
|
||||
public interface PlanPluginComponent {
|
||||
PlanCommand planCommand();
|
||||
|
||||
PlanSystem system();
|
||||
|
||||
PluginErrorLogger pluginErrorLogger();
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
Builder bindTemporaryDirectory(@Named("tempDir") Path tempDir);
|
||||
|
||||
@BindsInstance
|
||||
Builder plan(PlanPlugin plan);
|
||||
|
@ -18,18 +18,9 @@ package utilities.dagger;
|
||||
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.exceptions.EnableException;
|
||||
import com.djrapitops.plan.gathering.ServerSensor;
|
||||
import com.djrapitops.plan.gathering.listeners.ListenerSystem;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DatabaseSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.H2DB;
|
||||
import com.djrapitops.plan.storage.database.MySQLDB;
|
||||
import com.djrapitops.plan.storage.database.SQLiteDB;
|
||||
import com.djrapitops.plugin.logging.console.PluginLogger;
|
||||
import com.djrapitops.plugin.task.RunnableFactory;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@ -49,29 +40,6 @@ import static org.mockito.Mockito.when;
|
||||
@Module
|
||||
public class PluginSuperClassBindingModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
DBSystem provideDatabaseSystem(
|
||||
PlanConfig config,
|
||||
Locale locale,
|
||||
SQLiteDB.Factory sqLiteDB,
|
||||
H2DB.Factory h2Factory,
|
||||
MySQLDB mySQLDB,
|
||||
PluginLogger logger
|
||||
) {
|
||||
return new DBSystem(locale, sqLiteDB, h2Factory, logger) {
|
||||
@Override
|
||||
public void enable() throws EnableException {
|
||||
databases.add(sqLiteDB.usingDefaultFile());
|
||||
databases.add(h2Factory.usingDefaultFile());
|
||||
databases.add(mySQLDB);
|
||||
String dbType = config.get(DatabaseSettings.TYPE).toLowerCase().trim();
|
||||
db = getActiveDatabaseByName(dbType);
|
||||
super.enable();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
TaskSystem provideTaskSystem(RunnableFactory runnableFactory) {
|
||||
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package utilities.dagger;
|
||||
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import com.djrapitops.plugin.benchmarking.Timings;
|
||||
import com.djrapitops.plugin.command.ColorScheme;
|
||||
import com.djrapitops.plugin.logging.console.PluginLogger;
|
||||
import com.djrapitops.plugin.logging.console.TestPluginLogger;
|
||||
import com.djrapitops.plugin.logging.debug.ConsoleDebugLogger;
|
||||
import com.djrapitops.plugin.logging.debug.DebugLogger;
|
||||
import com.djrapitops.plugin.logging.error.ErrorHandler;
|
||||
import com.djrapitops.plugin.task.RunnableFactory;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import utilities.mocks.objects.TestRunnableFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module
|
||||
public class TestAPFModule {
|
||||
|
||||
@Provides
|
||||
@Named("currentVersion")
|
||||
@Singleton
|
||||
String provideCurrentVersion() {
|
||||
return "0.0.1";
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ColorScheme provideColorScheme() {
|
||||
return new ColorScheme(
|
||||
"", "", ""
|
||||
);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
DebugLogger provideDebugLogger(PluginLogger logger) {
|
||||
return new ConsoleDebugLogger(logger);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
PluginLogger providePluginLogger() {
|
||||
return new TestPluginLogger();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ErrorHandler provideErrorHandler(ErrorLogger errorLogger) {
|
||||
return errorLogger;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Timings provideTimings(DebugLogger debugLogger) {
|
||||
return new Timings(debugLogger);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
RunnableFactory provideRunnableFactory() {
|
||||
return new TestRunnableFactory();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package utilities.dagger;
|
||||
|
||||
import com.djrapitops.plan.settings.config.ExtensionSettings;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.LocaleSystem;
|
||||
import com.djrapitops.plan.storage.file.JarResource;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import utilities.TestResources;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Module
|
||||
public class TestSystemObjectProvidingModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Locale provideLocale(LocaleSystem localeSystem) {
|
||||
return localeSystem.getLocale();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ExtensionSettings providePluginsConfigSection(PlanConfig config) {
|
||||
return config.getExtensionSettings();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("isExtensionEnabled")
|
||||
Predicate<String> provideExtensionEnabledConfigCheck(PlanConfig config) {
|
||||
return config.getExtensionSettings()::isEnabled;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
JarResource.StreamFunction provideJarStreamFunction(@Named("tempDir") Path tempDir) {
|
||||
return resource -> {
|
||||
File copyTo = tempDir.resolve(resource).toFile();
|
||||
TestResources.copyResourceIntoFile(copyTo, "/" + resource);
|
||||
return new FileInputStream(copyTo);
|
||||
};
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("dataFolder")
|
||||
File provideDataFolder(@Named("tempDir") Path tempDir) {
|
||||
return tempDir.toFile();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ErrorLogger provideErrorLogger() {
|
||||
return (level, throwable, context) -> {
|
||||
throw new AssertionError("Test had an Exception: " + level.name() + " " + throwable.toString() + " " + context, throwable);
|
||||
};
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package utilities.mocks;
|
||||
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.utilities.logging.PluginErrorLogger;
|
||||
import utilities.dagger.DaggerPlanPluginComponent;
|
||||
import utilities.dagger.PlanPluginComponent;
|
||||
|
||||
@ -50,9 +51,20 @@ public class PluginMockComponent {
|
||||
}
|
||||
|
||||
public PlanSystem getPlanSystem() throws Exception {
|
||||
if (component == null) {
|
||||
component = DaggerPlanPluginComponent.builder().plan(getPlanMock()).build();
|
||||
}
|
||||
initComponent();
|
||||
return component.system();
|
||||
}
|
||||
|
||||
private void initComponent() throws Exception {
|
||||
if (component == null) {
|
||||
component = DaggerPlanPluginComponent.builder()
|
||||
.bindTemporaryDirectory(tempDir)
|
||||
.plan(getPlanMock()).build();
|
||||
}
|
||||
}
|
||||
|
||||
public PluginErrorLogger getPluginErrorLogger() throws Exception {
|
||||
initComponent();
|
||||
return component.pluginErrorLogger();
|
||||
}
|
||||
}
|
@ -50,7 +50,10 @@ public class NukkitAFKListener implements Listener {
|
||||
private final ErrorLogger errorLogger;
|
||||
|
||||
@Inject
|
||||
public NukkitAFKListener(PlanConfig config, ErrorLogger errorLogger) {
|
||||
public NukkitAFKListener(
|
||||
PlanConfig config,
|
||||
ErrorLogger errorLogger
|
||||
) {
|
||||
this.errorLogger = errorLogger;
|
||||
this.ignorePermissionInfo = new HashMap<>();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user