Arquillian+OSGi+Test Different Framework Properties - java

I have an OSGi bundle which reads some properties from the config.properties file of Apache Felix during the activation process, and if this configuration is malformed or absent then the bundle should not start. For this, I am creating its respective Unit Test, I am using Arquillian for the tests. The problem arises when I want to provide different types of conf.properties to different Arquillian tests, in order to cover each scenario.
When Arquillian runs the tests it load a framework.properties file from the /test/resources/ folder to initialize Apache Felix, install the test bundles and run the tests. Now, my question is how can I provide a different framework.properties file for each test case?
Here is the Arquillian Unit Test I use:
#RunWith(Arquillian.class)
public class PersistenceLoaderTest {
#Deployment
public static Archive<?> createDeployment() {
final JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "persistence-arq.jar");
archive.addClass(ProviderLoader.class);
archive.setManifest(new Asset() {
public InputStream openStream() {
OSGiManifestBuilder builder = OSGiManifestBuilder.newInstance();
builder.addBundleSymbolicName(archive.getName());
builder.addBundleManifestVersion(2);
builder.addImportPackages("org.osgi.service.startlevel", "org.osgi.service.url");
builder.addImportPackages(ProviderLoader.class);
return builder.openStream();
}
});
return archive;
}
#ArquillianResource
public Bundle bundle;
#ArquillianResource
BundleContext bundleContext;
#Test
public void loadFrameworkConfiguration(){
// What goes here?
}
}
And the framework.properties file:
# The Felix Framewok log level
#
# ERROR = 1;
# WARNING = 2;
# INFO = 3;
# DEBUG = 4;
felix.log.level=4
org.domain.database=mydb
org.domain.driver=org.hsqldb.jdbcDriver
org.domain.url=jdbc:hsqldb:file:
org.domain.username=sa
org.domain.password=
These are the property values I need to change and test them for different scenarios.

To my understanding this is Container level properties and not Deployment level properties, so you would need to restart the container for it to take effect.
You could achieve this by setting the Container mode in arquillian.xml to manual.
<arquillian>
<container qualifier="manual_felix" mode="manual">
</container>
</arquillian>
Then in a TestClass you can inject the ContainerController and start it with new properties for each run.
#RunWith(Arquillian.class)
public class TestA {
#Deployment(name = "x", managed = false) #TargetsContainer("manual_felix")
public static Archive<?> deployment() {
return ShrinkWrap.create....
}
#ArquillianResource
private ContainerController cc;
#ArquillianResource
private Deployer d;
#Test #InSequence(1)
public void start() {
cc.start("manual_felix", new Config().add("frameworkProperties", "my-custom-properties-file"));
d.deploy("x");
}
#Test #InSequence(2) #OperatesOnDeployment("x")
public void shouldDoSomethingInsideX() {
// executes inside container in context of X
}
}

Related

Spring Boot customize server.tomcat.threads.min-spare for management server only

I have an application with management.server enabled:
management.server.port=8081
When I start application, I have:
10 threads for 8080 HTTP nio connector
10 threads for 8081 HTTP nio connector
But I would like to reduce min-spare only for management (8081) and not for the web application (8080)
Looking at Spring code, it seems it's not possible, can someone confirm ?
EDIT: The approach below is not sufficient as the ManagementWebServerFactoryCustomizer is also a ConfigurableWebServerFactory and will thus be applied to the main server.
Adding logic to check againgst the management port is not helping as the management context has its very own wiring and won't pick up the bean.
Looks like it's not possible to hook into the management server configuration easily (would be easier if ServletManagementContextFactory were public).
You can look into ServletManagementChildContextConfiguration to see how the management server is wired.
You could hook into the management server configuration by providing a ManagementWebServerFactoryCustomizer like this (not sure if there's an easier way):
#Configuration
public class TomcatManagementCustomizerConfiguration {
#Bean
ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> servletManagementWebServerFactoryCustomizer(
#Value("${management.server.threads.min-spare:5}") int managementMinSpareThreads,
ListableBeanFactory beanFactory) {
return new TomcatManagementCustomizer(beanFactory, managementMinSpareThreads);
}
static class TomcatManagementCustomizer extends ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private final int managementMinSpareThreads;
protected TomcatManagementCustomizer(ListableBeanFactory beanFactory, int managementMinSpareThreads) {
super(beanFactory, TomcatWebServerFactoryCustomizer.class);
this.managementMinSpareThreads = managementMinSpareThreads;
}
#Override
#SuppressWarnings("rawtypes")
protected void customize(ConfigurableServletWebServerFactory factory, ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
super.customize(factory, managementServerProperties, serverProperties);
((TomcatServletWebServerFactory) factory).addConnectorCustomizers((connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol protocol = (AbstractProtocol) handler;
protocol.setMinSpareThreads(managementMinSpareThreads);
}
});
}
}
}
Can you not just put the following in either properties file or YAML file?
Or is there something I misunderstood?
server.tomcat.threads.min-spare=2
(This is for properties file)
Just to verify (You don't need this as you have been checking the updated value in the log)
Put the following in either properties file or YAML file
management.endpoints.web.exposure.include=health,info,metrics,env
(This is for properties file)
And visit /actuator/env/server.tomcat.threads.min-spare
You need actuator dependency for the link above to work.
You can use #ManagementConfigurationContext and add the configuration class to to your META-INF/spring.properties file.
It is also important to place the configuration class in a package which is not the main package or sub-package of your main application context. This is so that this configuration only applies to the management context.
Below is the sampel configuration following #Holgzn's response.
#ManagementContextConfiguration
public class TomcatManagementCustomizerConfiguration {
#Bean
ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> servletManagementWebServerFactoryCustomizer(
#Value("${management.server.threads.min-spare:5}") int managementMinSpareThreads,
ListableBeanFactory beanFactory) {
return new TomcatManagementCustomizer(beanFactory, managementMinSpareThreads);
}
static class TomcatManagementCustomizer extends ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private final int managementMinSpareThreads;
protected TomcatManagementCustomizer(ListableBeanFactory beanFactory, int managementMinSpareThreads) {
super(beanFactory, TomcatWebServerFactoryCustomizer.class);
this.managementMinSpareThreads = managementMinSpareThreads;
}
#Override
#SuppressWarnings("rawtypes")
protected void customize(ConfigurableServletWebServerFactory factory, ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
super.customize(factory, managementServerProperties, serverProperties);
((TomcatServletWebServerFactory) factory).addConnectorCustomizers((connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol protocol = (AbstractProtocol) handler;
protocol.setMinSpareThreads(managementMinSpareThreads);
}
});
}
}
}
The spring.properties file
org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=<package>.TomcatManagementCustomizerConfiguration

Could not find an 'annotation declaring class' for unit tests

I am setting up an Spring boot application on Jenkins. For the unit tests i am getting below error. This error is not particular to one test cases. Every time I run it is giving me error for different test. I am not sure what is wrong. Same project is working fine (build and unit tests) on local and other environments like (development, stage). Any idea with below errors?
00:49:42.836 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.abc.services.tokens.crypto.aws.AesGcmDynamoCryptoCipherProviderTest]
00:49:42.836 [main] INFO org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener#43195e57, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener#333291e3, org.springframework.test.context.support.DependencyInjectionTestExecutionListener#479d31f3, org.springframework.test.context.support.DirtiesContextTestExecutionListener#40ef3420]
Here is the test class
#SuppressWarnings("unchecked")
public class AesGcmDynamoCryptoCipherProviderTest extends AbstractTestNGBeanMockingTests {
#MockBean
AwsCrypto awsCrypto;
#MockBean
DynamoDBProvider dynamoDBProvider;
#MockBean
MasterKeyProvider masterKeyProvider;
#MockBean
Table table;
private static Item mockCipherItem(UUID cipherId) {
Item item = mock(Item.class);
return item;
}
private static <T> CryptoResult<T, ?> mockCryptoResult(T result) {
// do something
return cryptoResult;
}
#BeforeMethod
private void init() {
CryptoResult<String, ?> decryptoResult = mockCryptoResult(Base64.getEncoder().encodeToString("*decrypted*".getBytes()));
CryptoResult<String, ?> encryptoResult = mockCryptoResult("*encrypted*");
}
#Test
public void testGetCipher() {
AesGcmDynamoCryptoCipherProvider provider = new AesGcmDynamoCryptoCipherProvider("table", awsCrypto, dynamoDBProvider, masterKeyProvider);
UUID cipherId = UUID.randomUUID();
Item cipherItem = mockCipherItem(cipherId);
AesGcmCipher cipher = provider.getCipher(cipherId);
assertNotNull(cipher);
assertEquals(cipher.getCipherId(), cipherId);
}
}
Base class
#ContextConfiguration(classes = { //...
AbstractTestNGBeanMockingTests.MockBeanConfiguration.class //...
})
#DirtiesContext
public class AbstractTestNGBeanMockingTests extends AbstractTestNGSpringContextTests {
private static ThreadLocal<Class<? extends AbstractTestNGBeanMockingTests>> currentTestClass = new ThreadLocal<>();
#AfterClass(alwaysRun = true)
#Override
protected void springTestContextAfterTestClass() throws Exception {
super.springTestContextAfterTestClass();
}
#BeforeClass(alwaysRun = true, dependsOnMethods = { "springTestContextBeforeTestClass" })
#Override
protected void springTestContextPrepareTestInstance() throws Exception {
currentTestClass.set(this.getClass());
super.springTestContextPrepareTestInstance();
currentTestClass.set(null);
}
#BeforeMethod
public void initializeMockedBeans() {
MockBeanRegistration.initializeMockedBeans(this);
}
protected static class MockBeanConfiguration {
MockBeanConfiguration(ApplicationContext context) {
MockBeanRegistration.registerMocks((BeanDefinitionRegistry) context, currentTestClass.get());
}
}
}
I have bumped into this error after moving classes into new packages somewhere under the java folder, but omitting to move the corresponding test classes in the test folders.
After applying the changes in the test packages as well, it runs again.
You wrote that you experience the problem only in the Jenkins environment.
My guess is that Jenkins starts always with a new checkout of the project from a 100% clean status. In the other environments you might have some residues from the previous development, and these somehow allow the tests to 'work', but I would expect that it is Jenkins getting it right...
Try to setup the app in a development environment from scratch. If you get the error, so you will properly analyze it and correct it.

#SpringBootTest + #BeforeAll

I have a small spring boot app with database and rabbitmq usages.
So I would like to test with integration test (H2 + apache qpid).
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = TestSpringConfig.class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
As my app expect database and mq Im using #BeforeAll to start it:
#BeforeAll
public void before() {
startMessageBroker();
startDatabase();
}
The problem is that my web app starts before database/mq defined in #BeforeAll.
org.springframework.test.context.junit.jupiter.SpringExtension:
public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor,
BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback,
ParameterResolver {
// ...
#Override
public void beforeAll(ExtensionContext context) throws Exception {
getTestContextManager(context).beforeTestClass();
}
// ...
#Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
getTestContextManager(context).prepareTestInstance(testInstance);
}
// ...
Web app starts in postProcessTestInstance phase and #BeforeAll methods in beforeAll.
org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor:
private void execute(TestDescriptor testDescriptor, C parentContext, ExecutionTracker tracker) {
Node<C> node = asNode(testDescriptor);
tracker.markExecuted(testDescriptor);
C preparedContext;
try {
preparedContext = node.prepare(parentContext); // 1 <<<
SkipResult skipResult = node.shouldBeSkipped(preparedContext);
if (skipResult.isSkipped()) {
this.listener.executionSkipped(testDescriptor, skipResult.getReason().orElse("<unknown>"));
return;
}
}
catch (Throwable throwable) {
rethrowIfBlacklisted(throwable);
// We call executionStarted first to comply with the contract of EngineExecutionListener
this.listener.executionStarted(testDescriptor);
this.listener.executionFinished(testDescriptor, TestExecutionResult.failed(throwable));
return;
}
this.listener.executionStarted(testDescriptor);
TestExecutionResult result = singleTestExecutor.executeSafely(() -> {
C context = preparedContext;
try {
context = node.before(context); // 2 <<<
C contextForDynamicChildren = context;
context = node.execute(context, dynamicTestDescriptor -> {
this.listener.dynamicTestRegistered(dynamicTestDescriptor);
execute(dynamicTestDescriptor, contextForDynamicChildren, tracker);
});
C contextForStaticChildren = context;
// #formatter:off
testDescriptor.getChildren().stream()
.filter(child -> !tracker.wasAlreadyExecuted(child))
.forEach(child -> execute(child, contextForStaticChildren, tracker));
// #formatter:on
}
finally {
node.after(context);
}
});
this.listener.executionFinished(testDescriptor, result);
}
See points 1 and 2. There are executions of 'prepare' and then 'before'.
Im not sure is it issue of junit, SpringExtension or Im doing something wrong.
Any advice?
junit-jupiter: 5.0.1
spring-test: 5.0.0.RELEASE
spring-boot-test: 1.5.8.RELEASE
Checkout https://www.testcontainers.org/ it provides integration with JUnit to launch RabbitMQ and a database in docker containers as part of the JUnit testing. This makes integration tests much realistic because you using the same versions of database and message queue would be using in production.
This is by design, I think. Try to add the Bean post-processor/Context initializer to init/start your DB/rabbitMQ..
Is there any reason to start the DB and the message broker in test class? It seems to me that this is wrong by design. They both should be started along with your application context since they are part of your infrastructure.
It's not a responsibility of your tests to set up your infrastructure!
IMHO, a better way of doing things is the following:
Use H2 dependency with a test scope in maven + configure a starter in a way it starts H2 when the application context is starting
Start apache qpid (preferably embedded) on application start
In #Before just make sure you clean up stuff before running a test case
JUnit 5 [#BeforeAll] annotation is replacement of #BeforeClass annotation in JUnit 4.
It is used to signal that the annotated method should be executed before all tests in the current test class.
#BeforeAll should be used in static method
For More reading:
http://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

Conditional Deployment in Arquillian

Looking at the Arquillian documentation, I am aware that I can use the #ArquillianSuiteDeployment and #Deployment annotations to deploy my desired jars/wars to the container. Example:
#ArquillianSuiteDeployment
public class MyDeployer {
#Deployment(name = "myapp", order = 1, testable = false)
public static Archive<?> myDeploymentJar() {
final File file = Maven.configureResolver().fromFile(System.getProperty("settings.xml"))
.loadPomFromFile("pom.xml").resolve("com.myapp:test-app").withoutTransitivity()
.asSingleFile();
final JavaArchive jar = ShrinkWrap.createFromZipFile(JavaArchive.class, file);
final JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "test-app.jar").merge(jar);
return archive;
}
}
Let's infer that I have two more jars that I would like to deploy, but never together during the same test, JAR-A and JAR-C.
#Test
#RunAsClient
public void testOne() {
// deploy JAR-A before all others, but do not deploy JAR-C
}
#Test
#RunAsClient
public void testTwo() {
// deploy JAR-C before all others, but do not deploy JAR-A
}
Is it possible to introduce a conditional #Deployment that would go along with my tests?
That's an interesting usecase.
Deployments happen before execution of the tests so it's not possible at the moment to achieve it just by using annotations.
You can, however, programmatically control deployments using Deployer service.
#Deployment(name = "JAR-C", managed = false)
public static WebArchive create() {
return ShrinkWrap.create(JavaArchive.class);
}
#ArquillianResource
private Deployer deployer;
#Test
public void should_work() {
deployer.deploy("JAR-C");
// test
deploy.undeploy("JAR-C");
}
Important fact is that you have to instruct Arquillian to not manage this deployment for you, that's what managed = false flag is for in #Deployment(name = "X", managed = false).
Hope that helps.

Create a TestServer with an already started FakeApplication

I've started integrating in some Selenium tests into the testing framework in Play. I have a class in which I define a lot of special config settings for a FakeApplication, then create that FakeApplication using:
public abstract class FakeApplicationTest {
public static FakeApplication createFakeApp() {
// grab the main application.conf file that is used to start up the Play application
Config config = ConfigFactory.parseFile(new File("conf/application.conf"));
// resolve all variables within this config file with other variables within itself
config = ConfigFactory.load(config);
// create a Configuration object out of this and then turn it into a Map to be modified
Configuration configuration = new Configuration(config);
Map<String, Object> fakeApplicationConf = Maps.newHashMap(configuration.asMap());
...
// CUSTOM CONFIG THINGS HERE
...
return Helpers.fakeApplication(fakeApplicationConf);
}
}
What I would like to be able to do, is use this FakeApplication, start it in a #Before method (using JUnit 4), and then pass that already running FakeApplication to the TestServer which is needed to run the Selenium tests.
public class Checkout extends FluentTest {
public WebDriver webDriver = new FirefoxDriver();
...
public FakeApplication app;
#Before
public void beforeTest() {
app = FakeApplicationTest.createFakeApp();
Helpers.start(app);
FakeApplicationTest.createCleanDb();
...
}
#Test
public void testReviewPage()
running(testServer(3333, app), webDriver, browser -> {
...
}
}
}
What seems to happen when I do this though, is that the existing, running FakeApplication gets ignored/tossed and a new FakeApplication is created and started which is not setup with my custom fakeApplicationConf Map... or, it's stopping the app and restarting it but goes back to only use my default application.conf.
Any ideas on why this is? or if I can somehow accomplish this in a different way?

Categories