Skip to content

Commit

Permalink
Issue #559: Make sure that reflection based test data generation does…
Browse files Browse the repository at this point in the history
… not silently omit implementations.

Signed-off-by: Juergen Fickel <juergen.fickel@bosch.io>
  • Loading branch information
Juergen Fickel committed Oct 19, 2021
1 parent ee95beb commit f3fc081
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.eclipse.ditto.connectivity.service.messaging.httppush;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

import java.util.HashMap;
Expand All @@ -26,6 +27,7 @@
import org.eclipse.ditto.things.model.signals.commands.ThingCommandResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.Mockito;
Expand All @@ -36,57 +38,93 @@
* The same is ensured for implementations of {@link MessageCommand} with the associated implementations of
* {@link MessageCommandResponse}.
*/
@RunWith(Parameterized.class)
@RunWith(Enclosed.class)
public final class CommandAndCommandResponseMatchingValidatorParameterizedTest {

@Parameterized.Parameter
public SignalWithEntityId<?> command;
private static final SignalInterfaceImplementations MESSAGE_COMMANDS =
SignalInterfaceImplementations.newInstance(MessageCommand.class);
private static final SignalInterfaceImplementations MESSAGE_COMMAND_RESPONSES =
SignalInterfaceImplementations.newInstance(MessageCommandResponse.class);
private static final SignalInterfaceImplementations THING_COMMANDS =
SignalInterfaceImplementations.newInstance(ThingCommand.class);
private static final SignalInterfaceImplementations THING_COMMAND_RESPONSES =
SignalInterfaceImplementations.newInstance(ThingCommandResponse.class);

@Parameterized.Parameter(1)
public CommandResponse<?> commandResponse;
public static final class ReflectionBasedInstantiationTest {

private ConnectionLogger connectionLoggerMock;
private CommandAndCommandResponseMatchingValidator underTest;
@Test
public void allMessageCommandsCouldBeInstantiated() {
assertThat(MESSAGE_COMMANDS.getFailures()).isEmpty();
}

@Before
public void before() {
connectionLoggerMock = Mockito.mock(ConnectionLogger.class);
underTest = CommandAndCommandResponseMatchingValidator.newInstance(connectionLoggerMock);
}
@Test
public void allMessageCommandResponsesCouldBeInstantiated() {
assertThat(MESSAGE_COMMAND_RESPONSES.getFailures()).isEmpty();
}

@Parameterized.Parameters(name = "Command: {0}, response: {1}")
public static Object[][] getParameters() {
final Map<SignalWithEntityId<?>, SignalWithEntityId<?>> commandsAndResponses = new HashMap<>();
commandsAndResponses.putAll(getCommandsAndResponses(MessageCommand.class, MessageCommandResponse.class));
commandsAndResponses.putAll(getCommandsAndResponses(ThingCommand.class, ThingCommandResponse.class));
final var commandAndResponsesEntrySet = commandsAndResponses.entrySet();
return commandAndResponsesEntrySet.stream()
.map(entry -> new SignalWithEntityId<?>[]{entry.getKey(), entry.getValue()})
.toArray(SignalWithEntityId<?>[][]::new);
}
@Test
public void allThingCommandsCouldBeInstantiated() {
assertThat(THING_COMMANDS.getFailures()).isEmpty();
}

@Test
public void allThingCommandResponsesCouldBeInstantiated() {
assertThat(THING_COMMAND_RESPONSES.getFailures()).isEmpty();
}

private static <C extends SignalWithEntityId<?>, R extends SignalWithEntityId<?>> Map<SignalWithEntityId<?>, SignalWithEntityId<?>> getCommandsAndResponses(
final Class<C> commandInterfaceClass,
final Class<R> commandResponseInterfaceClass
) {
final Map<SignalWithEntityId<?>, SignalWithEntityId<?>> result = new HashMap<>();

final var commands = SignalInterfaceImplementations.newInstance(commandInterfaceClass);
final var commandResponses = SignalInterfaceImplementations.newInstance(commandResponseInterfaceClass);
commands.forEach(command -> {
final var commandClass = command.getClass();
final var commandClassSimpleName = commandClass.getSimpleName();
commandResponses.getSignalBySimpleClassName(commandClassSimpleName + "Response")
.ifPresent(commandResponse -> result.put(command, commandResponse));
});

return result;
}

@Test
public void commandAndItsResponseMatch() {
assertThatCode(() -> underTest.accept(command, commandResponse)).doesNotThrowAnyException();
Mockito.verifyNoInteractions(connectionLoggerMock);
@RunWith(Parameterized.class)
public static final class ParameterizedTest {

@Parameterized.Parameter
public SignalWithEntityId<?> command;

@Parameterized.Parameter(1)
public CommandResponse<?> commandResponse;

private ConnectionLogger connectionLoggerMock;
private CommandAndCommandResponseMatchingValidator underTest;

@Before
public void before() {
connectionLoggerMock = Mockito.mock(ConnectionLogger.class);
underTest = CommandAndCommandResponseMatchingValidator.newInstance(connectionLoggerMock);
}

@Parameterized.Parameters(name = "Command: {0}, response: {1}")
public static Object[][] getParameters() {
final Map<SignalWithEntityId<?>, SignalWithEntityId<?>> commandsAndResponses = new HashMap<>();
commandsAndResponses.putAll(getCommandsAndResponses(MESSAGE_COMMANDS, MESSAGE_COMMAND_RESPONSES));
commandsAndResponses.putAll(getCommandsAndResponses(THING_COMMANDS, THING_COMMAND_RESPONSES));
final var commandAndResponsesEntrySet = commandsAndResponses.entrySet();
return commandAndResponsesEntrySet.stream()
.map(entry -> new SignalWithEntityId<?>[]{entry.getKey(), entry.getValue()})
.toArray(SignalWithEntityId<?>[][]::new);
}

private static Map<SignalWithEntityId<?>, SignalWithEntityId<?>> getCommandsAndResponses(
final SignalInterfaceImplementations commands,
final SignalInterfaceImplementations commandResponses
) {
final Map<SignalWithEntityId<?>, SignalWithEntityId<?>> result = new HashMap<>();

commands.forEach(command -> {
final var commandClass = command.getClass();
final var commandClassSimpleName = commandClass.getSimpleName();
commandResponses.getSignalBySimpleClassName(commandClassSimpleName + "Response")
.ifPresent(commandResponse -> result.put(command, commandResponse));
});

return result;
}

@Test
public void commandAndItsResponseMatch() {
assertThatCode(() -> underTest.accept(command, commandResponse)).doesNotThrowAnyException();
Mockito.verifyNoInteractions(connectionLoggerMock);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
package org.eclipse.ditto.connectivity.service.messaging.httppush;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.concurrent.Immutable;

Expand All @@ -27,8 +27,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import scala.util.Try;

/**
* Holds an instance for each actual sub-class of the interface class provided to {@link #newInstance(Class)}.
*/
Expand All @@ -38,9 +36,13 @@ final class SignalInterfaceImplementations implements Iterable<SignalWithEntityI
private static final Logger LOGGER = LoggerFactory.getLogger(SignalInterfaceImplementations.class);

private final List<SignalWithEntityId<?>> signals;
private final List<Throwable> failures;

private SignalInterfaceImplementations(final List<SignalWithEntityId<?>> signals,
final List<Throwable> failures) {

private SignalInterfaceImplementations(final List<SignalWithEntityId<?>> signals) {
this.signals = signals;
this.signals = List.copyOf(signals);
this.failures = List.copyOf(failures);
}

static <T extends SignalWithEntityId<?>> SignalInterfaceImplementations newInstance(final Class<T> signalInterfaceClass) {
Expand All @@ -49,17 +51,22 @@ static <T extends SignalWithEntityId<?>> SignalInterfaceImplementations newInsta
throw new IllegalArgumentException(MessageFormat.format("<{0}> is not an interface.",
signalInterfaceClass.getName()));
}
return new SignalInterfaceImplementations(instantiateImplementingSignals(signalInterfaceClass));
}

private static List<SignalWithEntityId<?>> instantiateImplementingSignals(
final Class<? extends SignalWithEntityId<?>> signalInterfaceClass
) {
return SignalImplementationClassFinder.findImplementationClasses(signalInterfaceClass)
final List<SignalWithEntityId<?>> successful = new ArrayList<>();
final List<Throwable> failed = new ArrayList<>();

SignalImplementationClassFinder.findImplementationClasses(signalInterfaceClass)
.map(ReflectionBasedSignalInstantiator::tryToInstantiateSignal)
.filter(Try::isSuccess)
.map(Try::get)
.collect(Collectors.toUnmodifiableList());
.forEach(aTry -> {
if (aTry.isSuccess()) {
successful.add(aTry.get());
} else {
final var failure = aTry.failed();
failed.add(failure.get());
}
});

return new SignalInterfaceImplementations(successful, failed);
}

Optional<SignalWithEntityId<?>> getSignalBySimpleClassName(final CharSequence expectedSimpleClassName) {
Expand All @@ -75,6 +82,10 @@ Optional<SignalWithEntityId<?>> getSignalBySimpleClassName(final CharSequence ex
return result;
}

List<Throwable> getFailures() {
return failures;
}

@NotNull
@Override
public Iterator<SignalWithEntityId<?>> iterator() {
Expand Down

0 comments on commit f3fc081

Please sign in to comment.