Feature assert events within tests (#341)
* Count and assert repository events within a test. Signed-off-by: Jonathan Philip Knoblauch <JonathanPhilip.Knoblauch@bosch-si.com>
This commit is contained in:
committed by
Kai Zimmermann
parent
c1e5689f6a
commit
9b42c8cf57
@@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
||||
import org.eclipse.hawkbit.repository.test.util.JpaTestRepositoryManagement;
|
||||
import org.eclipse.hawkbit.repository.test.util.TestContextProvider;
|
||||
import org.eclipse.hawkbit.repository.test.util.TestRepositoryManagement;
|
||||
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
|
||||
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.cache.guava.GuavaCacheManager;
|
||||
import org.springframework.cloud.bus.ConditionalOnBusEnabled;
|
||||
import org.springframework.cloud.bus.ServiceMatcher;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.AdviceMode;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -102,7 +104,7 @@ public class TestConfiguration implements AsyncConfigurer {
|
||||
|
||||
/**
|
||||
* Bean for the download id cache.
|
||||
*
|
||||
*
|
||||
* @return the cache
|
||||
*/
|
||||
@Bean
|
||||
@@ -161,7 +163,7 @@ public class TestConfiguration implements AsyncConfigurer {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return the protostuff io message converter
|
||||
*/
|
||||
@Bean
|
||||
@@ -170,4 +172,13 @@ public class TestConfiguration implements AsyncConfigurer {
|
||||
return new BusProtoStuffMessageConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TestContextProvider} bean.
|
||||
*
|
||||
* @return a new {@link TestContextProvider}
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationContextAware applicationContextProvider() {
|
||||
return new TestContextProvider();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package org.eclipse.hawkbit.repository.test.matcher;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.test.util.TestContextProvider;
|
||||
import org.junit.Assert;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.event.ApplicationEventMulticaster;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.jayway.awaitility.Awaitility;
|
||||
import com.jayway.awaitility.core.ConditionTimeoutException;
|
||||
|
||||
/**
|
||||
* Test rule to setup and verify the event count for a method.
|
||||
*/
|
||||
public class EventVerifier implements TestRule {
|
||||
|
||||
private EventCaptor eventCaptor;
|
||||
|
||||
@Override
|
||||
public Statement apply(final Statement test, final Description description) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
|
||||
final Optional<Expect[]> expectedEvents = getExpectationsFrom(description);
|
||||
expectedEvents.ifPresent(events -> beforeTest());
|
||||
try {
|
||||
test.evaluate();
|
||||
expectedEvents.ifPresent(events -> afterTest(events));
|
||||
} finally {
|
||||
expectedEvents.ifPresent(listener -> removeEventListener());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Optional<Expect[]> getExpectationsFrom(final Description description) {
|
||||
return ofNullable(description.getAnnotation(ExpectEvents.class)).map(ExpectEvents::value);
|
||||
}
|
||||
|
||||
private void beforeTest() {
|
||||
final ConfigurableApplicationContext context = TestContextProvider.getContext();
|
||||
eventCaptor = new EventCaptor();
|
||||
context.addApplicationListener(eventCaptor);
|
||||
}
|
||||
|
||||
private void afterTest(final Expect[] expectedEvents) {
|
||||
verifyRightCountOfEvents(expectedEvents);
|
||||
verifyAllEventsCounted(expectedEvents);
|
||||
}
|
||||
|
||||
private void verifyRightCountOfEvents(final Expect[] expectedEvents) {
|
||||
|
||||
for (final Expect expectedEvent : expectedEvents) {
|
||||
try {
|
||||
Awaitility.await().atMost(5, SECONDS).until(() -> eventCaptor.getCountFor(expectedEvent.type()),
|
||||
equalTo(expectedEvent.count()));
|
||||
|
||||
} catch (final ConditionTimeoutException ex) {
|
||||
Assert.fail("Did not receive the expected amount of events form " + expectedEvent.type() + " Expected: "
|
||||
+ expectedEvent.count() + " but was: " + eventCaptor.getCountFor(expectedEvent.type()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyAllEventsCounted(final Expect[] expectedEvents) {
|
||||
|
||||
final Set<Class<?>> diffSet = eventCaptor.diff(expectedEvents);
|
||||
if (diffSet.size() > 0) {
|
||||
final StringBuilder failMessage = new StringBuilder("Missing event verification for ");
|
||||
final Iterator<Class<?>> itr = diffSet.iterator();
|
||||
while (itr.hasNext()) {
|
||||
final Class<?> element = itr.next();
|
||||
final int count = eventCaptor.getCountFor(element);
|
||||
failMessage.append(element + " with count: " + count + " ");
|
||||
}
|
||||
Assert.fail(failMessage.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void removeEventListener() {
|
||||
final ApplicationEventMulticaster multicaster = TestContextProvider.getContext()
|
||||
.getBean(ApplicationEventMulticaster.class);
|
||||
multicaster.removeApplicationListener(eventCaptor);
|
||||
}
|
||||
|
||||
private static class EventCaptor implements ApplicationListener<RemoteApplicationEvent> {
|
||||
|
||||
private final Multiset<Class<?>> capturedEvents = HashMultiset.create();
|
||||
|
||||
@Override
|
||||
public synchronized void onApplicationEvent(final RemoteApplicationEvent event) {
|
||||
capturedEvents.add(event.getClass());
|
||||
}
|
||||
|
||||
public synchronized int getCountFor(final Class<?> expectedEvent) {
|
||||
return capturedEvents.count(expectedEvent);
|
||||
}
|
||||
|
||||
public synchronized Set<Class<?>> diff(final Expect[] allEvents) {
|
||||
return Sets.difference(capturedEvents.elementSet(),
|
||||
java.util.stream.Stream.of(allEvents).map((e) -> e.type()).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package org.eclipse.hawkbit.repository.test.matcher;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Interface to add annotations for counting events by type and count.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface Expect {
|
||||
|
||||
/**
|
||||
* @return the type of the event
|
||||
*/
|
||||
Class<?> type();
|
||||
|
||||
/**
|
||||
* @return the expected count of events
|
||||
*/
|
||||
int count() default 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package org.eclipse.hawkbit.repository.test.matcher;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Interface to annotate methods when event count is required.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface ExpectEvents {
|
||||
|
||||
/**
|
||||
* @return a list of {@link Expect}
|
||||
*/
|
||||
Expect[] value() default {};
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.test.util;
|
||||
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
|
||||
import static org.junit.rules.RuleChain.outerRule;
|
||||
|
||||
import org.eclipse.hawkbit.ExcludePathAwareShallowETagFilter;
|
||||
import org.eclipse.hawkbit.TestConfiguration;
|
||||
@@ -29,6 +30,7 @@ import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.EventVerifier;
|
||||
import org.eclipse.hawkbit.security.DosFilter;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
@@ -37,11 +39,12 @@ import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.MethodRule;
|
||||
import org.junit.rules.TestWatchman;
|
||||
import org.junit.rules.RuleChain;
|
||||
import org.junit.rules.TestWatcher;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.bus.ServiceMatcher;
|
||||
@@ -79,7 +82,7 @@ import de.flapdoodle.embed.mongo.MongodExecutable;
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
|
||||
@TestPropertySource(properties = { "spring.data.mongodb.port=0", "spring.mongodb.embedded.version=3.2.7" })
|
||||
public abstract class AbstractIntegrationTest implements EnvironmentAware {
|
||||
protected static Logger LOG = null;
|
||||
private final static Logger LOG = LoggerFactory.getLogger(AbstractIntegrationTest.class);
|
||||
|
||||
protected static final Pageable pageReq = new PageRequest(0, 400);
|
||||
|
||||
@@ -151,9 +154,6 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware {
|
||||
@Autowired
|
||||
protected SystemSecurityContext systemSecurityContext;
|
||||
|
||||
@Autowired
|
||||
protected TestRepositoryManagement testRepositoryManagement;
|
||||
|
||||
protected MockMvc mvc;
|
||||
|
||||
protected SoftwareModuleType osType;
|
||||
@@ -174,9 +174,33 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware {
|
||||
@Autowired
|
||||
protected ServiceMatcher serviceMatcher;
|
||||
|
||||
@Rule
|
||||
// Cleaning repository will fire "delete" events. We won't count them to the
|
||||
// test execution. So there is order between both rules:
|
||||
public RuleChain ruleChain = outerRule(new CleanRepositoryRule()).around(new EventVerifier());
|
||||
|
||||
@Rule
|
||||
public final WithSpringAuthorityRule securityRule = new WithSpringAuthorityRule();
|
||||
|
||||
@Rule
|
||||
public TestWatcher testLifecycleLoggerRule = new TestWatcher() {
|
||||
|
||||
@Override
|
||||
protected void starting(final Description description) {
|
||||
LOG.info("Starting Test {}...", description.getMethodName());
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void succeeded(final Description description) {
|
||||
LOG.info("Test {} succeeded.", description.getMethodName());
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void failed(final Throwable e, final Description description) {
|
||||
LOG.error("Test {} failed with {}.", description.getMethodName(), e);
|
||||
}
|
||||
};
|
||||
|
||||
protected Environment environment = null;
|
||||
|
||||
@Override
|
||||
@@ -207,11 +231,6 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware {
|
||||
standardDsType = securityRule.runAsPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType());
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
testRepositoryManagement.clearTestRepository();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanCurrentCollection() {
|
||||
operations.delete(new Query());
|
||||
@@ -225,30 +244,6 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware {
|
||||
"/rest/v1/softwaremodules/{smId}/artifacts/{artId}/download", "/*/controller/artifacts/**"));
|
||||
}
|
||||
|
||||
@Rule
|
||||
public MethodRule watchman = new TestWatchman() {
|
||||
@Override
|
||||
public void starting(final FrameworkMethod method) {
|
||||
if (LOG != null) {
|
||||
LOG.info("Starting Test {}...", method.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded(final FrameworkMethod method) {
|
||||
if (LOG != null) {
|
||||
LOG.info("Test {} succeeded.", method.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Throwable e, final FrameworkMethod method) {
|
||||
if (LOG != null) {
|
||||
LOG.error("Test {} failed with {}.", method.getName(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static CIMySqlTestDatabase tesdatabase;
|
||||
|
||||
@BeforeClass
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.test.util;
|
||||
|
||||
import org.junit.rules.ExternalResource;
|
||||
|
||||
public class CleanRepositoryRule extends ExternalResource {
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
final TestRepositoryManagement repository = TestContextProvider.getContext()
|
||||
.getBean(TestRepositoryManagement.class);
|
||||
repository.clearTestRepository();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.test.util;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
public class TestContextProvider implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
public static ConfigurableApplicationContext getContext() {
|
||||
return (ConfigurableApplicationContext) applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(final ApplicationContext context) {
|
||||
applicationContext = context;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user