Skip to content

Commit

Permalink
Implement GlobalLogCollector for automatic collecting logs when test …
Browse files Browse the repository at this point in the history
…fails

Signed-off-by: David Kornel <[email protected]>
  • Loading branch information
kornys committed Jul 4, 2024
1 parent 5676820 commit dedbe7a
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Skodjob authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
package io.skodjob.testframe.annotations;

import io.skodjob.testframe.listeners.GlobalLogCollector;
import org.junit.jupiter.api.extension.ExtendWith;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* This annotation is used to automatically call log collecting
* when test of prepare and post phase fails in JUnit tests.
* It is applied at the class level.
* <p>
* It uses the {@link GlobalLogCollector}
*/
@Target(ElementType.TYPE)
@Retention(RUNTIME)
@Inherited
@ExtendWith(GlobalLogCollector.class)
public @interface CollectLogs {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright Skodjob authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
package io.skodjob.testframe.listeners;

import io.skodjob.testframe.LogCollector;
import io.skodjob.testframe.annotations.CollectLogs;
import io.skodjob.testframe.interfaces.ThrowableRunner;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;

import java.util.LinkedList;
import java.util.List;

/**
* Represents global log collector which is automatically called on text fail or error even in setup or post methods
*/
public class GlobalLogCollector implements TestExecutionExceptionHandler, LifecycleMethodExecutionExceptionHandler {
private static final Logger LOGGER = LogManager.getLogger(GlobalLogCollector.class);
private static LogCollector globalInstance;
private static final List<ThrowableRunner> COLLECT_CALLBACKS = new LinkedList<>();

/**
* Private constructor
*/
private GlobalLogCollector() {
// empty constructor
}

/**
* Handler when test fails
*
* @param extensionContext extension context
* @param throwable throwable
* @throws Throwable original throwable
*/
@Override
public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable {
saveKubeState();
throw throwable;
}

/**
* Handles beforeAll exception
*
* @param context extensionContext
* @param throwable throwable
* @throws Throwable original throwable
*/
@Override
public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable)throws Throwable {
saveKubeState();
LifecycleMethodExecutionExceptionHandler.super.handleBeforeAllMethodExecutionException(context, throwable);
}

/**
* Handles beforeEach exception
*
* @param context extensionContext
* @param throwable throwable
* @throws Throwable original throwable
*/
@Override
public void handleBeforeEachMethodExecutionException(ExtensionContext context,
Throwable throwable) throws Throwable {
saveKubeState();
LifecycleMethodExecutionExceptionHandler.super.handleBeforeEachMethodExecutionException(context, throwable);
}

/**
* Handles afterEach exception
*
* @param context extensionContext
* @param throwable throwable
* @throws Throwable original throwable
*/
@Override
public void handleAfterEachMethodExecutionException(ExtensionContext context,
Throwable throwable) throws Throwable {
saveKubeState();
LifecycleMethodExecutionExceptionHandler.super.handleAfterEachMethodExecutionException(context, throwable);
}

/**
* Handles afterAll exception
*
* @param context extensionContext
* @param throwable throwable
* @throws Throwable original throwable
*/
@Override
public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
saveKubeState();
LifecycleMethodExecutionExceptionHandler.super.handleAfterAllMethodExecutionException(context, throwable);
}

/**
* Setup globalLogCollector which is automatically used within {@link CollectLogs} annotation
*
* @param globalLogCollector log collector instance
*/
public static void setupGlobalLogCollector(LogCollector globalLogCollector) {
globalInstance = globalLogCollector;
}

/**
* Returns globalLogCollector instance
*
* @return global log collector instance
*/
public static LogCollector getGlobalLogCollector() {
if (globalInstance == null) {
throw new NullPointerException("Global log collector is not initialized");
}
return globalInstance;
}

/**
* Adds callback for running log collecting
*
* @param callback callback method with log collecting
*/
public static void addLogCallback(ThrowableRunner callback) {
COLLECT_CALLBACKS.add(callback);
}

private void saveKubeState() {
try {
for (ThrowableRunner runner : COLLECT_CALLBACKS) {
runner.run();
}
} catch (Exception ex) {
LOGGER.error("Cannot collect all data", ex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
*/
package io.skodjob.testframe.test.integration;

import io.skodjob.testframe.LogCollectorBuilder;
import io.skodjob.testframe.annotations.CollectLogs;
import io.skodjob.testframe.listeners.GlobalLogCollector;
import io.skodjob.testframe.utils.LoggerUtils;
import io.skodjob.testframe.annotations.ResourceManager;
import io.skodjob.testframe.annotations.TestVisualSeparator;
Expand All @@ -13,32 +16,58 @@
import io.skodjob.testframe.resources.ServiceAccountType;
import io.skodjob.testframe.utils.KubeUtils;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicBoolean;

@ResourceManager
@CollectLogs
@TestVisualSeparator
public abstract class AbstractIT {
static AtomicBoolean isCreateHandlerCalled = new AtomicBoolean(false);
static AtomicBoolean isDeleteHandlerCalled = new AtomicBoolean(false);
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm");
public static final Path LOG_DIR = Paths.get(System.getProperty("user.dir"), "target", "logs")
.resolve("test-run-" + DATE_FORMAT.format(LocalDateTime.now()));

static {
// Register resources which KRM uses for handling instead of native status check
KubeResourceManager.getInstance().setResourceTypes(
new NamespaceType(),
new ServiceAccountType(),
new DeploymentType()
);

// Register callback which are called with every create resource method for every resource
KubeResourceManager.getInstance().addCreateCallback(r -> {
isCreateHandlerCalled.set(true);
if (r.getKind().equals("Namespace")) {
KubeUtils.labelNamespace(r.getMetadata().getName(), "test-label", "true");
}
});

// Register callback which are called with every delete resource method for every resource
KubeResourceManager.getInstance().addDeleteCallback(r -> {
isDeleteHandlerCalled.set(true);
if (r.getKind().equals("Namespace")) {
LoggerUtils.logResource("Deleted", r);
}
});

// Setup global log collector and handlers
GlobalLogCollector.setupGlobalLogCollector(new LogCollectorBuilder()
.withNamespacedResources("sa", "deployment", "configmaps", "secret")
.withClusterWideResources("nodes")
.withKubeClient(KubeResourceManager.getKubeClient())
.withKubeCmdClient(KubeResourceManager.getKubeCmdClient())
.withRootFolderPath(LOG_DIR.toString())
.build());
GlobalLogCollector.addLogCallback(() -> {
GlobalLogCollector.getGlobalLogCollector().collectFromNamespaces("default");
GlobalLogCollector.getGlobalLogCollector().collectClusterWideResources();
});
}

protected String nsName1 = "test";
Expand Down

0 comments on commit dedbe7a

Please sign in to comment.