package io.eguan.dtx;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import io.eguan.dtx.DtxResourceManager;
import io.eguan.dtx.DtxResourceManagerContext;
import io.eguan.dtx.DtxTaskStatus;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.transaction.xa.XAException;
import org.hamcrest.Matcher;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
/**
* Utilities for configuring mockito mocks.
*
* @author oodrive
* @author pwehrle
*
*/
final class DtxMockUtils {
/**
* Maximum verification timeout for mock implementations.
*/
static final int VERIFY_TIMEOUT_MS = 20000;
private DtxMockUtils() {
throw new AssertionError("Not instantiable");
}
/**
* Matcher wrapping a {@link Comparator} comparing to a reference object of the given type.
*
* The {@link Comparator} determines the matching result by returning either 0 for matching objects or any non-zero
* value otherwise upon calling {@link Comparator#compare(Object, Object)}.
*
*
* @param <T>
* the type for which a {@link Comparator} must be provided
*/
static final class CompMatcher<T> extends ArgumentMatcher<T> {
private final T original;
private final Comparator<T> comparator;
/**
* Constructs an instance with a reference object and a {@link Comparator}.
*
* @param original
* a non-<code>null</code> reference object of type T
* @param comparator
* a non-<code>null</code> {@link Comparator}
*/
CompMatcher(@Nonnull final T original, @Nonnull final Comparator<T> comparator) {
this.original = Objects.requireNonNull(original);
this.comparator = Objects.requireNonNull(comparator);
}
@SuppressWarnings("unchecked")
@Override
public final boolean matches(final Object argument) {
if (argument == null) {
return false;
}
return this.comparator.compare(original, (T) argument) == 0;
}
}
/**
* Matcher for comparing the {@link DtxResourceManagerContext#getResourceManagerId() resource manager ID} contained
* in a {@link DtxResourceManagerContext} instance.
*
*
*/
static final class ResourceIdMatcher extends ArgumentMatcher<DtxResourceManagerContext> {
private final UUID referenceId;
/**
* Constructs an instance with a reference ID against which to compare.
*
* @param referenceId
* a non-<code>null</code> {@link UUID} to compare against
*/
ResourceIdMatcher(@Nonnull final UUID referenceId) {
this.referenceId = Objects.requireNonNull(referenceId);
}
@Override
public final boolean matches(final Object argument) {
if (!(argument instanceof DtxResourceManagerContext)) {
return false;
}
return referenceId.equals(((DtxResourceManagerContext) argument).getResourceManagerId());
}
}
/**
* Basic matcher only used to produce appropriate generic type arguments for Mockito.argThat(Matcher) calls.
*
*
* @param <T>
* the generic type to produce
*/
static final class WrapMatcher<T> extends ArgumentMatcher<T> {
private final Matcher<?> wrapped;
/**
* Constructs a new instance wrapping the provided {@link Matcher}.
*
* @param wrapped
* the non-<code>null</code> {@link Matcher} to wrap
*/
WrapMatcher(@Nonnull final Matcher<?> wrapped) {
this.wrapped = wrapped;
}
@Override
public final boolean matches(final Object argument) {
return wrapped.matches(argument);
}
}
/**
* Matcher for {@link DtxResourceManagerContext}s being in one of the declared valid states.
*
* Any {@link DtxResourceManagerContext} whose {@link DtxResourceManagerContext#getTxStatus()} returns one of the
* valid states matches.
*
*
*/
static final class TxPositiveStateMatcher extends ArgumentMatcher<DtxResourceManagerContext> {
private final List<DtxTaskStatus> validStates;
/**
* Constructs an instance with respect to a list of valid states.
*
* @param validStates
* a list of {@link DtxTaskStatus}
*/
TxPositiveStateMatcher(final DtxTaskStatus... validStates) {
this.validStates = Arrays.asList(validStates);
}
@Override
public final boolean matches(final Object argument) {
if (!(argument instanceof DtxResourceManagerContext)) {
return false;
}
return validStates.contains(((DtxResourceManagerContext) argument).getTxStatus());
}
}
/**
* Matcher for {@link DtxResourceManagerContext}s not being in one of the declared invalid states.
*
* Any {@link DtxResourceManagerContext} whose {@link DtxResourceManagerContext#getTxStatus()} does not return one
* of the invalid states matches.
*
*
*/
static final class TxNegativeStateMatcher extends ArgumentMatcher<DtxResourceManagerContext> {
private final List<DtxTaskStatus> invalidStates;
/**
* Constructs an instance with respect to a list of invalid states.
*
* @param invalidStates
* a list of {@link DtxTaskStatus}
*/
TxNegativeStateMatcher(final DtxTaskStatus... invalidStates) {
this.invalidStates = Arrays.asList(invalidStates);
}
@Override
public final boolean matches(final Object argument) {
if (!(argument instanceof DtxResourceManagerContext)) {
return false;
}
return !invalidStates.contains(((DtxResourceManagerContext) argument).getTxStatus());
}
}
/**
* Verifies successful in-order execution of one transaction on a given {@link DtxResourceManager}.
*
* @param targetResMgr
* the target {@link DtxResourceManager}
* @param totalTimes
* the total of times a transaction completed successfully on the given mock
* @throws XAException
* if mock setup fails, not part of this test
*/
static final void verifySuccessfulTxExecution(final DtxResourceManager targetResMgr, final int totalTimes)
throws XAException {
verify(targetResMgr, timeout(VERIFY_TIMEOUT_MS).times(totalTimes)).commit(any(DtxResourceManagerContext.class));
verify(targetResMgr, atLeast(totalTimes)).start(any(byte[].class));
verify(targetResMgr, atLeast(totalTimes)).prepare(any(DtxResourceManagerContext.class));
// skips checking for invocation order on multiple invocations as a perfect order cannot be guaranteed with
// concurrently executed transactions
if (totalTimes > 1) {
return;
}
final InOrder orderVerifier = inOrder(targetResMgr);
orderVerifier.verify(targetResMgr).start(any(byte[].class));
orderVerifier.verify(targetResMgr).prepare(any(DtxResourceManagerContext.class));
orderVerifier.verify(targetResMgr).commit(any(DtxResourceManagerContext.class));
}
/**
* Verifies a transaction was rolled back on the given {@link DtxResourceManager}.
*
* @param targetResMgr
* the target {@link DtxResourceManager}
* @param totalTimes
* the total number of calls to verify
* @param afterPrepare
* if the rollback call must be done after prepare
* @throws XAException
* if mock setup fails, not part of this test
*/
static final void verifyRollbackOnTx(final DtxResourceManager targetResMgr, final int totalTimes,
final boolean afterPrepare) throws XAException {
verify(targetResMgr, timeout(VERIFY_TIMEOUT_MS).times(totalTimes)).rollback(
any(DtxResourceManagerContext.class));
verify(targetResMgr, atLeast(totalTimes)).start(any(byte[].class));
if (afterPrepare) {
verify(targetResMgr, atLeast(totalTimes)).prepare(any(DtxResourceManagerContext.class));
}
verify(targetResMgr, never()).commit(any(DtxResourceManagerContext.class));
final InOrder orderVerifier = inOrder(targetResMgr);
orderVerifier.verify(targetResMgr).start(any(byte[].class));
if (afterPrepare) {
orderVerifier.verify(targetResMgr).prepare(any(DtxResourceManagerContext.class));
}
orderVerifier.verify(targetResMgr).rollback(any(DtxResourceManagerContext.class));
}
}