Skip to content

Fix integration test cleanup #1295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.cloudfoundry;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Bean;
import org.springframework.test.annotation.DirtiesContext;

/**
* Meta-annotation to show that a test class will add too much to the CF instance, and that a full universe
* cleanup should occur. This is important because otherwise the integration tests create too many apps and
* blow up the memory quota. We do not want to recreate the full environment for EVERY test class because
* the process takes 30~60s, so in total it could add more than an hour to the integration tests.
* <p>
* Technically, this is achieved by recreating a Spring ApplicationContext with {@link DirtiesContext}. The
* {@link CloudFoundryCleaner} bean will be destroyed, which triggers {@link CloudFoundryCleaner#clean()}.
* After that, every {@link Bean} in {@link IntegrationTestConfiguration} is recreated, including users,
* clients, organizations, etc: everything required to run a test.
* <p>
* We use a meta-annotation instead of a raw {@link DirtiesContext} to make it clear what it does, rather
* than having to understand complicated lifecycle issues.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@DirtiesContext
public @interface CleanupCloudFoundryAfterClass {}
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,16 @@
import org.cloudfoundry.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuples;
import reactor.util.retry.Retry;

final class CloudFoundryCleaner {
final class CloudFoundryCleaner implements InitializingBean, DisposableBean {

private static boolean cleanSlateEnvironment = false;

private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client.test");

Expand Down Expand Up @@ -162,6 +166,25 @@ final class CloudFoundryCleaner {
this.uaaClient = uaaClient;
}

/**
* Once at the beginning of the whole test suite, clean up the environment. It should only ever happen
* once, hence the static init variable.
*/
@Override
public void afterPropertiesSet() {
if (!cleanSlateEnvironment) {
LOGGER.info(
"Performing clean slate cleanup. Should happen once per integration test run.");
this.clean();
cleanSlateEnvironment = true;
}
}

@Override
public void destroy() {
this.clean();
}

void clean() {
Flux.empty()
.thenMany(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.info.GetInfoRequest;
import org.cloudfoundry.client.v2.organizationquotadefinitions.CreateOrganizationQuotaDefinitionRequest;
Expand Down Expand Up @@ -243,14 +242,13 @@ String clientSecret(NameFactory nameFactory) {
return nameFactory.getClientSecret();
}

@Bean(initMethod = "clean", destroyMethod = "clean")
@Bean
CloudFoundryCleaner cloudFoundryCleaner(
@Qualifier("admin") CloudFoundryClient cloudFoundryClient,
NameFactory nameFactory,
@Qualifier("admin") NetworkingClient networkingClient,
Version serverVersion,
@Qualifier("admin") UaaClient uaaClient) {

return new CloudFoundryCleaner(
cloudFoundryClient, nameFactory, networkingClient, serverVersion, uaaClient);
}
Expand Down Expand Up @@ -339,8 +337,8 @@ ReactorLogCacheClient logCacheClient(
}

@Bean
RandomNameFactory nameFactory(Random random) {
return new RandomNameFactory(random);
RandomNameFactory nameFactory() {
return new RandomNameFactory(new SecureRandom());
}

@Bean(initMethod = "block")
Expand Down Expand Up @@ -447,11 +445,6 @@ String planName(NameFactory nameFactory) {
return nameFactory.getPlanName();
}

@Bean
SecureRandom random() {
return new SecureRandom();
}

@Bean
RoutingClient routingClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
return ReactorRoutingClient.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.client.CloudFoundryClient;
Expand Down Expand Up @@ -96,6 +97,7 @@
import reactor.test.StepVerifier;
import reactor.util.function.Tuple2;

@CleanupCloudFoundryAfterClass
public final class ApplicationsTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.time.Duration;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.ApplicationUtils;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.ServiceBrokerUtils;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.servicebrokers.CreateServiceBrokerRequest;
Expand All @@ -43,6 +44,7 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

@CleanupCloudFoundryAfterClass
public final class ServiceBrokersTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,26 @@

import java.time.Duration;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.ApplicationUtils;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v3.admin.ClearBuildpackCacheRequest;
import org.cloudfoundry.util.JobUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

public final class AdminTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;

// The buildpacks cache needs to be non-empty for the DELETE call to succeed.
// We pull the "testLogCacheApp" bean, which ensures the bean will be initialized,
// the app will be pushed, and it will add some data to the cache.
@Autowired private Mono<ApplicationUtils.ApplicationMetadata> testLogCacheApp;

@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_10)
@Test
public void clearBuildpackCache() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Collections;
import java.util.Map;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.client.CloudFoundryClient;
Expand Down Expand Up @@ -106,6 +107,7 @@
import reactor.test.StepVerifier;
import reactor.util.function.Tuples;

@CleanupCloudFoundryAfterClass
public final class ApplicationsTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.nio.file.Path;
import java.time.Duration;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.client.CloudFoundryClient;
Expand Down Expand Up @@ -51,6 +52,7 @@
import reactor.test.StepVerifier;

@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_4)
@CleanupCloudFoundryAfterClass
public final class DeploymentsTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.nio.file.Path;
import java.time.Duration;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.client.CloudFoundryClient;
Expand All @@ -42,6 +43,7 @@
import reactor.test.StepVerifier;

@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_0)
@CleanupCloudFoundryAfterClass
public final class ProcessesTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.time.Duration;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.ApplicationUtils;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.ServiceBrokerUtils;
Expand All @@ -49,6 +50,7 @@
import reactor.test.StepVerifier;

@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_10)
@CleanupCloudFoundryAfterClass
public final class ServiceBrokersTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException;
import java.time.Duration;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.client.CloudFoundryClient;
Expand Down Expand Up @@ -50,6 +51,7 @@
import reactor.test.StepVerifier;

@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_1_12)
@CleanupCloudFoundryAfterClass
public final class TasksTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,35 @@
import static org.cloudfoundry.util.DelayUtils.exponentialBackOff;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Random;
import java.util.function.Predicate;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.ApplicationUtils;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_9)
public class LogCacheTest extends AbstractIntegrationTest implements InitializingBean {
public class LogCacheTest extends AbstractIntegrationTest {

@Autowired LogCacheClient logCacheClient;

@Autowired Random random;

@Autowired private Mono<ApplicationUtils.ApplicationMetadata> testLogCacheApp;

private ApplicationUtils.ApplicationMetadata testLogCacheAppMetadata;

@Autowired private TestLogCacheEndpoints testLogCacheEndpoints;

@Override
public void afterPropertiesSet() {
this.testLogCacheAppMetadata = this.testLogCacheApp.block();
private final Random random = new SecureRandom();

@BeforeEach
void setUp(@Autowired Mono<ApplicationUtils.ApplicationMetadata> testLogCacheApp) {
this.testLogCacheAppMetadata = testLogCacheApp.block();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.List;
import java.util.Map;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.logcache.v1.Envelope;
Expand Down Expand Up @@ -87,11 +88,13 @@
import org.cloudfoundry.util.FluentMap;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

@CleanupCloudFoundryAfterClass
public final class ApplicationsTest extends AbstractIntegrationTest {

private static final String DEFAULT_ROUTER_GROUP = "default-tcp";
Expand All @@ -106,6 +109,12 @@ public final class ApplicationsTest extends AbstractIntegrationTest {

@Autowired private LogCacheClient logCacheClient;

// To create a service in #pushBindService, the Service Broker must be installed first.
// We ensure it is by loading the serviceBrokerId @Lazy bean.
@Qualifier("serviceBrokerId")
@Autowired
private Mono<String> serviceBrokerId;

@Test
public void copySource() throws IOException {
String sourceName = this.nameFactory.getApplicationName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Optional;
import java.util.function.Predicate;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.PushApplicationRequest;
import org.cloudfoundry.operations.domains.CreateDomainRequest;
Expand All @@ -49,6 +50,7 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

@CleanupCloudFoundryAfterClass
public final class RoutesTest extends AbstractIntegrationTest {

private static final String DEFAULT_ROUTER_GROUP = "default-tcp";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.time.Duration;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
import org.cloudfoundry.ServiceBrokerUtils;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.spaces.CreateSpaceRequest;
Expand All @@ -33,10 +34,12 @@
import org.cloudfoundry.util.ResourceUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

@CleanupCloudFoundryAfterClass
public final class ServiceAdminTest extends AbstractIntegrationTest {

@Autowired private CloudFoundryClient cloudFoundryClient;
Expand All @@ -53,6 +56,12 @@ public final class ServiceAdminTest extends AbstractIntegrationTest {

@Autowired private String serviceName;

// To create a service in #pushBindService, the Service Broker must be installed first.
// We ensure it is by loading the serviceBrokerId @Lazy bean.
@Qualifier("serviceBrokerId")
@Autowired
private Mono<String> serviceBrokerId;

@Test
public void disableServiceAccess() {
String planName = this.nameFactory.getPlanName();
Expand Down
Loading