TestIMdsalApiManager with naturally sorted flows
[genius.git] / mdsalutil / mdsalutil-api / src / test / java / org / opendaylight / genius / mdsalutil / interfaces / testutils / TestIMdsalApiManager.java
1 /*
2  * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.genius.mdsalutil.interfaces.testutils;
9
10 import static com.google.common.truth.Truth.assertThat;
11 import static org.junit.Assert.assertTrue;
12 import static org.opendaylight.mdsal.binding.testutils.AssertDataObjects.assertEqualBeans;
13 import static org.opendaylight.yangtools.testutils.mockito.MoreAnswers.realOrException;
14
15 import com.google.common.collect.ComparisonChain;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.Iterables;
18 import com.google.common.collect.Lists;
19 import com.google.common.util.concurrent.CheckedFuture;
20 import com.google.common.util.concurrent.Futures;
21 import java.math.BigInteger;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import org.mockito.Mockito;
26 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
27 import org.opendaylight.genius.mdsalutil.FlowEntity;
28 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * Fake IMdsalApiManager useful for tests.
34  *
35  * <p>Read e.g.
36  * http://googletesting.blogspot.ch/2013/07/testing-on-toilet-know-your-test-doubles.html
37  * and http://martinfowler.com/articles/mocksArentStubs.html for more background.
38  *
39  * <p>This class is abstract just to save reading lines and typing keystrokes to
40  * manually implement a bunch of methods we're not yet interested in.  Create instances
41  * of it using it's static {@link #newInstance()} method.
42  *
43  * @author Michael Vorburger
44  */
45 public abstract class TestIMdsalApiManager implements IMdsalApiManager {
46
47     private static final Logger LOG = LoggerFactory.getLogger(TestIMdsalApiManager.class);
48
49     private List<FlowEntity> flows;
50
51     public static TestIMdsalApiManager newInstance() {
52         return Mockito.mock(TestIMdsalApiManager.class, realOrException());
53     }
54
55     /**
56      * Get list of installed flows.
57      * Prefer the {@link #assertFlows(Iterable)} instead of using this and checking yourself.
58      * @return immutable copy of list of flows
59      */
60     public synchronized List<FlowEntity> getFlows() {
61         return ImmutableList.copyOf(getOrNewFlows());
62     }
63
64     private synchronized List<FlowEntity> getOrNewFlows() {
65         if (flows == null) {
66             flows = new ArrayList<>();
67         }
68         return flows;
69     }
70
71     public synchronized void assertFlows(Iterable<FlowEntity> expectedFlows) {
72         checkNonEmptyFlows(expectedFlows);
73         List<FlowEntity> nonNullFlows = getOrNewFlows();
74         if (!Iterables.isEmpty(expectedFlows)) {
75             assertTrue("No Flows created (bean wiring may be broken?)", !nonNullFlows.isEmpty());
76         }
77         // TODO Support Iterable <-> List directly within XtendBeanGenerator
78         List<FlowEntity> expectedFlowsAsNewArrayList = Lists.newArrayList(expectedFlows);
79         assertEqualBeans(expectedFlowsAsNewArrayList, nonNullFlows);
80     }
81
82
83     private synchronized void checkNonEmptyFlows(Iterable<FlowEntity> expectedFlows) {
84         if (!Iterables.isEmpty(expectedFlows)) {
85             assertTrue("No Flows created (bean wiring may be broken?)", !getOrNewFlows().isEmpty());
86         }
87     }
88
89     public synchronized void assertFlowsInAnyOrder(Iterable<FlowEntity> expectedFlows) {
90         checkNonEmptyFlows(expectedFlows);
91         // TODO Support Iterable <-> List directly within XtendBeanGenerator
92         List<FlowEntity> expectedFlowsAsNewArrayList = Lists.newArrayList(expectedFlows);
93
94         List<FlowEntity> sortedFlows = sortFlows(flows);
95         List<FlowEntity> sortedExpectedFlows = sortFlows(expectedFlowsAsNewArrayList);
96
97         // FYI: This containsExactlyElementsIn() assumes that FlowEntity, and everything in it,
98         // has correctly working equals() implementations.  assertEqualBeans() does not assume
99         // that, and would work even without equals, because it only uses property reflection.
100         // Normally this will lead to the same result, but if one day it doesn't (because of
101         // a bug in an equals() implementation somewhere), then it's worth to keep this diff
102         // in mind.
103
104         // FTR: This use of G Truth and then catch AssertionError and using assertEqualBeans iff NOK
105         // (thus discarding the message from G Truth) is a bit of a hack, but it works well...
106         // If you're tempted to improve this, please remember that correctly re-implementing
107         // containsExactlyElementsIn (or Hamcrest's similar containsInAnyOrder) isn't a 1 line
108         // trivia... e.g. a.containsAll(b) && b.containsAll(a) isn't sufficient, because it
109         // won't work for duplicates (which we frequently have here); and ordering before is
110         // not viable because FlowEntity is not Comparable, and Comparator based on hashCode
111         // is not a good idea (different instances can have same hashCode), and e.g. on
112         // System#identityHashCode even less so.
113         try {
114             assertThat(sortedFlows).containsExactlyElementsIn(sortedExpectedFlows);
115         } catch (AssertionError e) {
116             // We LOG the AssertionError just for clarity why containsExactlyElementsIn() failed
117             LOG.warn("assert containsExactlyElementsIn() failed", e);
118             // We LOG the expected and actual flow in case of a failed assertion
119             // because, even though that is typically just a HUGE String that's
120             // hard to read (the diff printed subsequently by assertEqualBeans
121             // is, much, more readable), there are cases when looking more closely
122             // at the full toString() output of the flows is still useful, so:
123             LOG.warn("assert failed [order ignored!]; expected flows: {}", sortedExpectedFlows);
124             LOG.warn("assert failed [order ignored!]; actual flows  : {}", sortedFlows);
125             // The point of this is basically just that our assertEqualBeans output,
126             // in case of a comparison failure, is *A LOT* more clearly readable
127             // than what G Truth (or Hamcrest) can do based on toString.
128             assertEqualBeans(sortedExpectedFlows, sortedFlows);
129         }
130     }
131
132     private List<FlowEntity> sortFlows(Iterable<FlowEntity> flowsToSort) {
133         List<FlowEntity> sortedFlows = Lists.newArrayList(flowsToSort);
134         Collections.sort(sortedFlows,
135             (flow1, flow2) -> ComparisonChain.start()
136                 .compare(flow1.getTableId(),  flow2.getTableId())
137                 .compare(flow1.getPriority(), flow2.getPriority())
138                 .compare(flow1.getFlowId(),   flow2.getFlowId())
139                 .result());
140         return sortedFlows;
141     }
142
143     @Override
144     public synchronized void installFlow(FlowEntity flowEntity) {
145         getOrNewFlows().add(flowEntity);
146     }
147
148     @Override
149     public synchronized CheckedFuture<Void, TransactionCommitFailedException> installFlow(BigInteger dpId,
150             FlowEntity flowEntity) {
151         installFlow(flowEntity);
152         return Futures.immediateCheckedFuture(null);
153     }
154
155     @Override
156     public synchronized CheckedFuture<Void, TransactionCommitFailedException> removeFlow(BigInteger dpnId,
157             FlowEntity flowEntity) {
158         getOrNewFlows().remove(flowEntity);
159         return Futures.immediateCheckedFuture(null);
160     }
161
162     @Override
163     public synchronized void batchedAddFlow(BigInteger dpId, FlowEntity flowEntity) {
164         getOrNewFlows().add(flowEntity);
165     }
166
167     @Override
168     public synchronized void batchedRemoveFlow(BigInteger dpId, FlowEntity flowEntity) {
169         getOrNewFlows().remove(flowEntity);
170     }
171
172 }