Skip to content

Commit

Permalink
fix(#1275): Add environment setting to enable/disable test actors
Browse files Browse the repository at this point in the history
  • Loading branch information
christophd committed Dec 3, 2024
1 parent 95e7225 commit 07f821c
Show file tree
Hide file tree
Showing 7 changed files with 585 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,40 +38,48 @@ public class KubernetesActor extends TestActor {
/** Kubernetes' connection state, checks connectivity to Kubernetes cluster */
private static AtomicBoolean connected;

private final KubernetesClient kubernetesClient;

public KubernetesActor(KubernetesClient kubernetesClient) {
setName("k8s");
super("k8s");

if (kubernetesClient != null) {
this.kubernetesClient = kubernetesClient;
} else {
this.kubernetesClient = new KubernetesClientBuilder().build();
synchronized (logger) {
if (connected == null) {
if (kubernetesClient != null) {
connected = new AtomicBoolean(verifyConnected(kubernetesClient));
} else {
try (KubernetesClient tempClient = new KubernetesClientBuilder().build()) {
connected = new AtomicBoolean(verifyConnected(tempClient));
}
}
}
}
}

@Override
public boolean isDisabled() {
synchronized (logger) {
if (connected == null) {
if (KubernetesSettings.isEnabled()) {
try {
Future<Boolean> future = Executors.newSingleThreadExecutor().submit(() -> {
kubernetesClient.pods().list();
return true;
});
if (!KubernetesSettings.isEnabled()) {
return true;
}

connected = new AtomicBoolean((future.get(KubernetesSettings.getConnectTimeout(), TimeUnit.MILLISECONDS)));
} catch (Exception e) {
logger.warn("Skipping Kubernetes action as no proper Kubernetes environment is available on host system!", e);
connected = new AtomicBoolean(false);
}
} else {
return false;
}
}
return !connected.get() || super.isDisabled();
}

return !connected.get();
public static boolean verifyConnected(KubernetesClient kubernetesClient) {
try {
Future<Boolean> future = Executors.newSingleThreadExecutor().submit(() -> {
kubernetesClient.pods().list();
return true;
});

return future.get(KubernetesSettings.getConnectTimeout(), TimeUnit.MILLISECONDS);
} catch (Exception e) {
logger.warn("Skipping Kubernetes action as no proper Kubernetes environment is available on host system!", e);
return false;
}
}

public static void resetConnectionState() {
synchronized (logger) {
connected = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright the original author or authors.
*
* 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 org.citrusframework.kubernetes;

import java.util.HashMap;

import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesCrudDispatcher;
import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
import io.fabric8.mockwebserver.Context;
import okhttp3.mockwebserver.MockWebServer;
import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class KubernetesActorTest {

private final KubernetesMockServer k8sServer = new KubernetesMockServer(new Context(), new MockWebServer(),
new HashMap<>(), new KubernetesCrudDispatcher(), false);

KubernetesClient k8sClient;

@BeforeClass
public void setupMocks() {
k8sServer.init();
k8sClient = k8sServer.createClient();
}

@AfterClass(alwaysRun = true)
public void stop() {
k8sServer.destroy();
}

@Test
public void shouldVerifyConnectedState() {
try {
Assert.assertFalse(new KubernetesActor(k8sClient).isDisabled());
} finally {
KubernetesActor.resetConnectionState();
}

try {
KubernetesClient k8sClientMock = Mockito.mock(KubernetesClient.class);
Assert.assertTrue(new KubernetesActor(k8sClientMock).isDisabled());
} finally {
KubernetesActor.resetConnectionState();
}
}

@Test
public void shouldOverruleConnectedState() {
boolean initial = KubernetesSettings.isEnabled();
try {
System.setProperty("citrus.kubernetes.enabled", "false");
Assert.assertTrue(new KubernetesActor(k8sClient).isDisabled());
} finally {
System.setProperty("citrus.kubernetes.enabled", Boolean.toString(initial));
}

initial = Boolean.parseBoolean(System.getProperty("citrus.test.actor.k8s.enabled", "true"));
try {
System.setProperty("citrus.test.actor.k8s.enabled", "false");
Assert.assertTrue(new KubernetesActor(k8sClient).isDisabled());
} finally {
System.setProperty("citrus.test.actor.k8s.enabled", Boolean.toString(initial));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,47 +41,54 @@ public class TestcontainersActor extends TestActor {
/** Docker's connection state, checks connectivity to Docker engine */
private static AtomicBoolean connected;

private final DockerClient dockerClient;

public TestcontainersActor() {
this(null);
}

public TestcontainersActor(DockerClient dockerClient) {
setName("testcontainers");
super("testcontainers");

if (dockerClient != null) {
this.dockerClient = dockerClient;
} else {
DockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
this.dockerClient = DockerClientImpl.getInstance(clientConfig,
new OkDockerHttpClient.Builder().dockerHost(clientConfig.getDockerHost()).build()
);
synchronized (logger) {
if (connected == null) {
if (dockerClient != null) {
connected = new AtomicBoolean(verifyConnected(dockerClient));
} else {
DockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
DockerClient tempClient = DockerClientImpl.getInstance(clientConfig,
new OkDockerHttpClient.Builder().dockerHost(clientConfig.getDockerHost()).build()
);
connected = new AtomicBoolean(verifyConnected(tempClient));
}
}
}
}

@Override
public boolean isDisabled() {
synchronized (logger) {
if (connected == null) {
if (TestContainersSettings.isEnabled()) {
try {
Future<Boolean> future = Executors.newSingleThreadExecutor().submit(() -> {
dockerClient.pingCmd().exec();
return true;
});
if (!TestContainersSettings.isEnabled()) {
return true;
}

connected = new AtomicBoolean((future.get(TestContainersSettings.getConnectTimeout(), TimeUnit.MILLISECONDS)));
} catch (Exception e) {
logger.warn("Skipping Docker test execution as no proper Docker environment is available on host system!", e);
connected = new AtomicBoolean(false);
}
} else {
return false;
}
}
return !connected.get() || super.isDisabled();
}

public static boolean verifyConnected(DockerClient dockerClient) {
try {
Future<Boolean> future = Executors.newSingleThreadExecutor().submit(() -> {
dockerClient.pingCmd().exec();
return true;
});

return !connected.get();
return (future.get(TestContainersSettings.getConnectTimeout(), TimeUnit.MILLISECONDS));
} catch (Exception e) {
logger.warn("Skipping Docker test execution as no proper Docker environment is available on host system!", e);
return false;
}
}

public static void resetConnectionState() {
synchronized (logger) {
connected = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright the original author or authors.
*
* 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 org.citrusframework.testcontainers;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.PingCmd;
import org.citrusframework.kubernetes.KubernetesSettings;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;

public class TestcontainersActorTest {

@Mock
DockerClient dockerClient;

@Mock
PingCmd connected;

@Mock
PingCmd disconnected;

@BeforeClass
public void setupMocks() {
MockitoAnnotations.openMocks(this);

doNothing().when(connected).exec();
doThrow(IllegalStateException.class).when(disconnected).exec();
}

@Test
public void shouldVerifyConnectedState() {
try {
when(dockerClient.pingCmd()).thenReturn(connected);
Assert.assertFalse(new TestcontainersActor(dockerClient).isDisabled());
} finally {
TestcontainersActor.resetConnectionState();
reset(dockerClient);
}

try {
when(dockerClient.pingCmd()).thenReturn(disconnected);
Assert.assertTrue(new TestcontainersActor(dockerClient).isDisabled());
} finally {
TestcontainersActor.resetConnectionState();
reset(dockerClient);
}
}

@Test
public void shouldOverruleConnectedState() {
boolean initial = KubernetesSettings.isEnabled();
try {
System.setProperty("citrus.testcontainers.enabled", "false");
when(dockerClient.pingCmd()).thenReturn(connected);
Assert.assertTrue(new TestcontainersActor(dockerClient).isDisabled());
} finally {
System.setProperty("citrus.testcontainers.enabled", Boolean.toString(initial));
reset(dockerClient);
}

initial = Boolean.parseBoolean(System.getProperty("citrus.test.actor.testcontainers.enabled", "true"));
try {
System.setProperty("citrus.test.actor.testcontainers.enabled", "false");
when(dockerClient.pingCmd()).thenReturn(connected);
Assert.assertTrue(new TestcontainersActor(dockerClient).isDisabled());
} finally {
System.setProperty("citrus.test.actor.testcontainers.enabled", Boolean.toString(initial));
}
}
}
33 changes: 29 additions & 4 deletions core/citrus-api/src/main/java/org/citrusframework/TestActor.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.citrusframework;

import java.util.Optional;

/**
* Actor performs send/receive message actions. With send/receive actors we can enable/disable Citrus message simulation
* very easily. This enables a fast switch in end-to-end testing when a simulated application suddenly is real and we have to disable
Expand All @@ -24,15 +26,31 @@
* @since 1.3
*/
public class TestActor {
/** The name of this actor*/

private static final String TEST_ACTOR_ENABLED_PROPERTY = "citrus.test.actor.%s.enabled";
private static final String TEST_ACTOR_ENABLED_ENV = "CITRUS_TEST_ACTOR_%s_ENABLED";

/** The name of this actor */
private String name;

/** Marks if this test actor should not participate in tests */
private boolean disabled = false;

public TestActor() {
}

public TestActor(String name) {
this.name = name;
}

public TestActor(String name, boolean disabled) {
this.name = name;
this.disabled = disabled;
}

/**
* Gets the name.
* @return the name the name to get.
* @return the name to get.
*/
public String getName() {
return name;
Expand All @@ -48,10 +66,17 @@ public void setName(String name) {

/**
* Gets the disabled.
* @return the disabled the disabled to get.
* @return the disabled to get.
*/
public boolean isDisabled() {
return disabled;
boolean enabled = true;
if (name != null && !name.isBlank()) {
// Check enabled state in System properties or environment variables for this test actor using its name
enabled = Boolean.parseBoolean(System.getProperty(TEST_ACTOR_ENABLED_PROPERTY.formatted(name.trim().toLowerCase()),
Optional.ofNullable(System.getenv(TEST_ACTOR_ENABLED_ENV.formatted(name.trim().toUpperCase()))).orElse("true")));
}

return !enabled || disabled;
}

/**
Expand Down
Loading

0 comments on commit 07f821c

Please sign in to comment.