All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.xdev.tci.leakdetection.TCILeakAgent Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2024 XDEV Software (https://xdev.software)
 *
 * 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 software.xdev.tci.leakdetection;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;

import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.Container;

import software.xdev.tci.TCI;
import software.xdev.tci.factory.TCIFactory;
import software.xdev.tci.factory.registry.TCIFactoryRegistry;
import software.xdev.tci.leakdetection.config.LeakDetectionConfig;
import software.xdev.tci.serviceloading.TCIServiceLoader;


/**
 * Detects infrastructure that was not disposed properly after all test have ended.
 * 

* If infrastructure is disposed asynchronously, {@link LeakDetectionAsyncReaper} may need to be used. *

*

* Active by default due to service loading. *

*/ public class TCILeakAgent implements TestExecutionListener { private static final Logger LOG = LoggerFactory.getLogger(TCILeakAgent.class); protected LeakDetectionConfig config; @Override public void testPlanExecutionStarted(final TestPlan testPlan) { this.config = TCIServiceLoader.instance().service(LeakDetectionConfig.class); if(!this.config.enabled()) { return; } LOG.debug("Registered"); } @SuppressWarnings({"java:S2629", "java:S106"}) @Override public void testPlanExecutionFinished(final TestPlan testPlan) { if(!this.config.enabled()) { return; } final List pendingReapers = ServiceLoader.load(LeakDetectionAsyncReaper.class) .stream() .map(ServiceLoader.Provider::get) .toList(); if(!pendingReapers.isEmpty()) { LOG.info("Waiting for reapers to finish..."); pendingReapers.forEach(LeakDetectionAsyncReaper::blockUntilReaped); } final Map, Set>> leaked = TCIFactoryRegistry.instance().getReturnedAndInUse(); if(leaked.isEmpty()) { LOG.info("No leaks detected"); return; } final String baseErrorMsg = "PANIC: DETECTED CONTAINER INFRASTRUCTURE LEAK"; final String logErrorMsg = "! " + baseErrorMsg + " !"; final String border = "!".repeat(logErrorMsg.length()); // Ensure that developer notices PANIC System.err.println(border); System.err.println(logErrorMsg); System.err.println(border); LOG.error(border); LOG.error(logErrorMsg); LOG.error(border); LOG.error( "All test are finished but some infrastructure is still marked as in use:\n{}", leaked.entrySet().stream() .map(e -> e.getKey().getClass().getSimpleName() + " leaked " + e.getValue().size() + "x " + "[container-ids=" + e.getValue().stream() .map(TCI::getContainer) .filter(Objects::nonNull) .map(Container::getContainerId) .toList() + "]") .collect(Collectors.joining("\n")) ); LOG.error("Please ensure that every TCI/TestContainerInfrastructure is closed after it's no longer in use"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy