
de.gematik.test.tiger.lib.TigerDirector Maven / Gradle / Ivy
/*
* Copyright 2024 gematik GmbH
*
* 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 de.gematik.test.tiger.lib;
import static de.gematik.test.tiger.common.config.TigerConfigurationKeys.*;
import static org.awaitility.Awaitility.await;
import com.google.common.annotations.VisibleForTesting;
import de.gematik.rbellogger.RbelOptions;
import de.gematik.rbellogger.util.IRbelMessageListener;
import de.gematik.rbellogger.util.RbelAnsiColors;
import de.gematik.test.tiger.LocalProxyRbelMessageListener;
import de.gematik.test.tiger.common.Ansi;
import de.gematik.test.tiger.common.banner.Banner;
import de.gematik.test.tiger.common.config.TigerConfigurationException;
import de.gematik.test.tiger.common.config.TigerGlobalConfiguration;
import de.gematik.test.tiger.common.data.config.tigerproxy.TigerProxyConfiguration;
import de.gematik.test.tiger.common.web.TigerBrowserUtil;
import de.gematik.test.tiger.lib.exception.TigerStartupException;
import de.gematik.test.tiger.lib.rbel.RbelMessageValidator;
import de.gematik.test.tiger.lib.reports.TigerRestAssuredCurlLoggingFilter;
import de.gematik.test.tiger.lib.serenityrest.SerenityRestUtils;
import de.gematik.test.tiger.testenvmgr.TigerTestEnvMgr;
import de.gematik.test.tiger.testenvmgr.TigerTestEnvMgrApplication;
import de.gematik.test.tiger.testenvmgr.controller.TestExecutionController;
import de.gematik.test.tiger.testenvmgr.data.BannerType;
import de.gematik.test.tiger.testenvmgr.env.ScenarioRunner;
import de.gematik.test.tiger.testenvmgr.env.TigerStatusUpdate;
import de.gematik.test.tiger.testenvmgr.servers.log.TigerServerLogManager;
import de.gematik.test.tiger.testenvmgr.util.TigerEnvironmentStartupException;
import de.gematik.test.tiger.testenvmgr.util.TigerTestEnvException;
import io.cucumber.core.plugin.report.SerenityReporterCallbacks;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.serenitybdd.rest.SerenityRest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.api.Fail;
import org.awaitility.core.ConditionTimeoutException;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
/**
* The TigerDirector is the public interface of the high level features of the Tiger test framework.
*
*
* - read and apply Tiger test framework configuration from tiger.yaml
*
- start workflow UI, Tiger test environment manager and local Tiger Proxy
*
*
* It also provides access to the Tiger test environment manager, the local Tiger Proxy and the
* Workflow UI interface.
*/
@SuppressWarnings("unused") // API
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TigerDirector {
@Getter private static TigerRestAssuredCurlLoggingFilter curlLoggingFilter;
private static TigerTestEnvMgr tigerTestEnvMgr;
private static boolean initialized = false;
@Getter private static TigerLibConfig libConfig;
private static ConfigurableApplicationContext envMgrApplicationContext;
public static synchronized void start() {
if (initialized) {
log.info("Tiger Director already started, skipping");
return;
}
try {
showTigerBanner();
readConfiguration();
registerRestAssuredFilter();
applyLoggingLevels();
applyTestLibConfig();
} catch (TigerConfigurationException tcex) {
throw tcex;
} catch (RuntimeException rte) {
throw new TigerConfigurationException(
"Unable to read/process configuration - " + rte.getMessage(), rte);
}
try {
// get free port
startTestEnvMgr();
startWorkflowUi();
setupTestEnvironment(Optional.empty());
setDefaultProxyToLocalTigerProxy();
initialized = true;
if (getTigerTestEnvMgr().isLocalTigerProxyActive()) {
LocalProxyRbelMessageListener.initialize();
if (libConfig.clearEnvironmentStartupTraffic) {
LocalProxyRbelMessageListener.getInstance().clearAllMessages();
}
}
} catch (RuntimeException e) {
initialized = false;
quit(true);
throw e;
}
}
public static synchronized void startStandaloneTestEnvironment() {
log.info("Starting Tiger testenvironment in STANDALONE MODE!");
if (initialized) {
log.info("Tiger Director already started, skipping");
return;
}
try {
showTigerBanner();
readConfiguration();
if (getLibConfig().isActivateWorkflowUi()) {
log.warn(
"Starting Workflow UI in standalone mode is not supported, deactivating the flag in"
+ " config");
getLibConfig().activateWorkflowUi = false;
}
applyLoggingLevels();
applyTestLibConfig();
} catch (RuntimeException rte) {
throw new TigerConfigurationException(
"Unable to read/process configuration - " + rte.getMessage(), rte);
}
try {
// get free port
startTestEnvMgr();
if (tigerTestEnvMgr.getConfiguration().isLocalProxyActive()) {
log.warn(
"Starting local Tiger Proxy in standalone mode is not supported, deactivating the flag"
+ " in config");
tigerTestEnvMgr.getConfiguration().setLocalProxyActive(false);
}
setupTestEnvironment(Optional.empty());
initialized = true;
} catch (RuntimeException e) {
initialized = false;
quit(true);
throw e;
}
}
private static void applyLoggingLevels() {
try {
TigerGlobalConfiguration.readMapWithCaseSensitiveKeys("tiger", "logging", "level")
.forEach(TigerServerLogManager::setLoggingLevel);
} catch (NoClassDefFoundError ncde) {
log.warn("Unable to detect logback library! Setting log level feature not supported");
}
// setLoggingLevel is sufficient for almost all cases. SpringBoot applications are a special
// case -
// SpringBoot resets the levels during startup.
// So When using SpringBootApplicationBuilder the respective properties have to be passed in
// manually!
// except of course for the main methods of the SpringBottApplication classes as there we expect
// to use application.yaml
}
private static boolean shutdownHookRegistered = false;
public static synchronized void registerShutdownHook() {
if (shutdownHookRegistered) {
return;
}
shutdownHookRegistered = true;
log.info("Registering shutdown hook...");
java.lang.Runtime.getRuntime()
.addShutdownHook(
new Thread(
() -> {
if (tigerTestEnvMgr == null) {
log.info(
Ansi.colorize(
"Finished shutdown (no test environment) OK", RbelAnsiColors.RED_BOLD));
return;
}
if (!tigerTestEnvMgr.isShuttingDown()) {
quit(true);
}
}));
}
public static void waitForAcknowledgedQuit() {
quit(true);
}
private static void quit(boolean shouldUserAcknowledgeShutdown) {
try {
if (getLibConfig() != null
&& getLibConfig().isActivateWorkflowUi()
&& shouldUserAcknowledgeShutdown) {
// This method is called in the shut down hook of the JVM and we experienced that using the
// logger
// sometimes kept this message from appearing in the console, so resorting to System.out
// here
System.out.println( // NOSONAR
Ansi.colorize(
"TGR Workflow UI is active, please press quit in browser window...",
RbelAnsiColors.GREEN_BOLD));
if (tigerTestEnvMgr != null) {
tigerTestEnvMgr.receiveTestEnvUpdate(
TigerStatusUpdate.builder()
.bannerMessage("Test run finished, press SHUTDOWN")
.bannerColor("green")
.bannerType(BannerType.TESTRUN_ENDED)
.build());
try {
await()
.pollInterval(1, TimeUnit.SECONDS)
.atMost(getLibConfig().getPauseExecutionTimeoutSeconds(), TimeUnit.SECONDS)
.until(
() ->
tigerTestEnvMgr.getUserConfirmQuit().get()
|| tigerTestEnvMgr.isShouldAbortTestExecution());
} finally {
tigerTestEnvMgr.shutDown();
}
}
} else if (tigerTestEnvMgr != null) {
tigerTestEnvMgr.receivedQuitConfirmationFromWorkflowUi();
System.out.println("TGR Shutting down test env..."); // NOSONAR
tigerTestEnvMgr.shutDown();
}
} finally {
unregisterRestAssuredFilter();
System.out.println("TGR Destroying spring boot context after testrun..."); // NOSONAR
if (envMgrApplicationContext != null) {
envMgrApplicationContext.close();
}
System.out.println("TGR Tiger shut down orderly"); // NOSONAR
}
}
private static void setupTestEnvironment(
Optional tigerProxyMessageListener) {
if (SKIP_ENVIRONMENT_SETUP.getValueOrDefault().equals(Boolean.FALSE)) {
log.info(
"\n" + Banner.toBannerStr("SETTING UP TESTENV...", RbelAnsiColors.BLUE_BOLD.toString()));
tigerTestEnvMgr.setUpEnvironment(tigerProxyMessageListener);
log.info("\n" + Banner.toBannerStr("TESTENV SET UP OK", RbelAnsiColors.BLUE_BOLD.toString()));
}
}
public static synchronized void readConfiguration() {
libConfig =
TigerGlobalConfiguration.instantiateConfigurationBeanStrict(
TigerLibConfig.class, "TIGER_LIB")
.orElseGet(TigerLibConfig::new);
}
private static void showTigerBanner() {
// created via https://kirilllive.github.io/ASCII_Art_Paint/ascii_paint.html
if (SHOW_TIGER_LOGO.getValueOrDefault().equals(Boolean.TRUE)) {
try {
log.info(
"\n"
+ IOUtils.toString(
Objects.requireNonNull(
TigerDirector.class.getResourceAsStream("/tiger2-logo.ansi")),
StandardCharsets.UTF_8));
} catch (IOException e) {
throw new TigerStartupException("Unable to read tiger logo!");
}
}
}
private static void applyTestLibConfig() {
if (libConfig.isRbelPathDebugging()) {
RbelOptions.activateRbelPathDebugging();
} else {
RbelOptions.deactivateRbelPathDebugging();
}
if (libConfig.isRbelAnsiColors()) {
RbelAnsiColors.activateAnsiColors();
} else {
RbelAnsiColors.deactivateAnsiColors();
}
}
private static synchronized void startTestEnvMgr() {
log.info(
"\n" + Banner.toBannerStr("STARTING TESTENV MGR...", RbelAnsiColors.BLUE_BOLD.toString()));
Map properties = TigerTestEnvMgr.getConfiguredLoggingLevels();
properties.put("server.port", TESTENV_MGR_RESERVED_PORT.getValueOrDefault());
properties.put("spring.mustache.enabled", false); // TGR-875 avoid warning in console
properties.put("spring.mustache.check-template-location", false);
properties.putAll(TigerTestEnvMgr.getTigerLibConfiguration());
envMgrApplicationContext =
new SpringApplicationBuilder()
.bannerMode(Mode.OFF)
.properties(properties)
.sources(TigerTestEnvMgrApplication.class)
.web(WebApplicationType.SERVLET)
.registerShutdownHook(false)
.initializers()
.run();
tigerTestEnvMgr = envMgrApplicationContext.getBean(TigerTestEnvMgr.class);
TestExecutionController testExecutionController =
envMgrApplicationContext.getBean(TestExecutionController.class);
testExecutionController.setShutdownListener(
() -> {
await().pollDelay(300, TimeUnit.MILLISECONDS).until(() -> true);
tigerTestEnvMgr.abortTestExecution();
quit(false);
});
testExecutionController.setPauseListener(
() -> SerenityReporterCallbacks.setPauseMode(!SerenityReporterCallbacks.isPauseMode()));
}
private static synchronized void startWorkflowUi() {
if (libConfig.activateWorkflowUi) {
log.info(
"\n"
+ Banner.toBannerStr(
"STARTING WORKFLOW UI ...", RbelAnsiColors.BLUE_BOLD.toString()));
if (libConfig.startBrowser) {
TigerBrowserUtil.openUrlInBrowser(
"http://localhost:"
+ TESTENV_MGR_RESERVED_PORT
.getValue()
.orElseThrow(
() -> new TigerEnvironmentStartupException("Failed to start browser!"))
.toString(),
"Workflow UI");
}
log.info("Waiting for workflow Ui to fetch status...");
try {
int duration = 10;
if (!libConfig.startBrowser) {
log.info(
"Workflow UI http://localhost:" + TESTENV_MGR_RESERVED_PORT.getValue().orElseThrow());
duration = 60;
}
await()
.atMost(Duration.ofSeconds(duration))
.pollInterval(Duration.ofSeconds(1))
.until(() -> tigerTestEnvMgr.isWorkflowUiSentFetch());
} catch (ConditionTimeoutException cte) {
libConfig.activateWorkflowUi = false;
throw new TigerTestEnvException("No feedback from workflow Ui, aborting!", cte);
}
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new TigerTestEnvException(
"Interrupt received while waiting for workflow Ui to become ready", e);
}
}
}
private static synchronized void setDefaultProxyToLocalTigerProxy() {
TigerProxyConfiguration tpCfg = tigerTestEnvMgr.getConfiguration().getTigerProxy();
// set proxy to local tiger proxy for test suites
if (tigerTestEnvMgr.isLocalTigerProxyActive()) {
tigerTestEnvMgr.getLocalTigerProxyOptional().ifPresent(SerenityRestUtils::setupSerenityRest);
tigerTestEnvMgr.setDefaultProxyToLocalTigerProxy();
} else {
log.info(
Ansi.colorize(
"SKIPPING TIGER PROXY settings as localProxyActive==false...",
RbelAnsiColors.RED_BOLD));
}
}
public static synchronized boolean isInitialized() {
return initialized;
}
public static TigerTestEnvMgr getTigerTestEnvMgr() {
assertThatTigerIsInitialized();
return tigerTestEnvMgr;
}
@SuppressWarnings("UnusedReturnValue") // API method
public static String getLocalTigerProxyUrl() {
assertThatTigerIsInitialized();
if (tigerTestEnvMgr.getLocalTigerProxyOptional().isEmpty()
|| !tigerTestEnvMgr.getConfiguration().isLocalProxyActive()) {
return null;
} else {
return tigerTestEnvMgr.getLocalTigerProxyOrFail().getBaseUrl();
}
}
public static void assertThatTigerIsInitialized() {
if (!initialized) {
throw new TigerStartupException(
"Tiger test environment has not been initialized successfully!");
}
}
public static boolean isSerenityAvailable() {
return TigerDirector.isSerenityAvailable(false);
}
public static boolean isSerenityAvailable(boolean quiet) {
try {
Class.forName("net.serenitybdd.core.Serenity");
return true;
} catch (ClassNotFoundException e) {
if (!quiet) {
log.warn(
"Trying to use Serenity functionality, but Serenity BDD packages are not declared as"
+ " runtime dependency.",
e);
}
return false;
}
}
@VisibleForTesting
@SneakyThrows
public static synchronized void testUninitialize() {
initialized = false;
tigerTestEnvMgr = null;
curlLoggingFilter = null;
RbelMessageValidator.clearInstance(); // NOSONAR - this is only called from test code
LocalProxyRbelMessageListener
.clearTestingInstance(); // NOSONAR - the method testUninitialize should also only be used
// for testing
System.clearProperty("TIGER_TESTENV_CFGFILE");
System.clearProperty("http.proxyHost");
System.clearProperty("https.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyPort");
ScenarioRunner.clearScenarios();
TigerGlobalConfiguration.reset();
}
public static synchronized void registerRestAssuredFilter() {
if (getLibConfig().isAddCurlCommandsForRaCallsToReport() && curlLoggingFilter == null) {
curlLoggingFilter = new TigerRestAssuredCurlLoggingFilter();
SerenityRest.filters(curlLoggingFilter);
}
}
public static synchronized void unregisterRestAssuredFilter() {
if (curlLoggingFilter != null) {
SerenityRest.replaceFiltersWith(new ArrayList<>());
}
curlLoggingFilter = null;
}
public static void pauseExecution() {
pauseExecution("", false);
}
public static void pauseExecution(String message, boolean isHtml) {
String defaultMessage = "Test execution paused, click to continue";
if (StringUtils.isBlank(message)) {
message = defaultMessage;
}
if (getLibConfig().isActivateWorkflowUi()) {
tigerTestEnvMgr.receiveTestEnvUpdate(
TigerStatusUpdate.builder()
.bannerMessage(message)
.bannerColor("green")
.bannerType(BannerType.STEP_WAIT)
.bannerIsHtml(isHtml)
.build());
await()
.pollInterval(1, TimeUnit.SECONDS)
.atMost(getLibConfig().getPauseExecutionTimeoutSeconds(), TimeUnit.SECONDS)
.until(
() -> tigerTestEnvMgr.getUserAcknowledgedOnWorkflowUi().compareAndSet(true, false));
} else {
throw new TigerTestEnvException(
"The step 'TGR pause test run execution with message \"{}\"' is not supported "
+ "outside the Workflow UI. Please check the manual for more information.",
message);
}
}
public static void pauseExecution(String message) {
pauseExecution(message, false);
}
public static void pauseExecutionAndFailIfDesired(String message, String errorMessage) {
if (getLibConfig().isActivateWorkflowUi()) {
tigerTestEnvMgr.receiveTestEnvUpdate(
TigerStatusUpdate.builder()
.bannerMessage(message)
.bannerColor("black")
.bannerType(BannerType.FAIL_PASS)
.build());
await()
.pollInterval(1, TimeUnit.SECONDS)
.atMost(getLibConfig().getPauseExecutionTimeoutSeconds(), TimeUnit.SECONDS)
.until(
() -> tigerTestEnvMgr.getUserAcknowledgedOnWorkflowUi().compareAndSet(true, false));
if (tigerTestEnvMgr.isUserPressedFailTestExecution()) {
Fail.fail(errorMessage);
}
} else {
throw new TigerTestEnvException(
"The step 'TGR pause test run execution with message \"{}\" and "
+ "message in case of error \"{}\"' is not supported outside the Workflow UI. "
+ "Please check the manual for more information.",
message,
errorMessage);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy