1
2
3
4
5
6 package mockit.internal.expectations;
7
8 import edu.umd.cs.findbugs.annotations.NonNull;
9 import edu.umd.cs.findbugs.annotations.Nullable;
10
11 import java.util.ArrayList;
12 import java.util.List;
13
14 import mockit.internal.expectations.invocation.ExpectedInvocation;
15 import mockit.internal.expectations.invocation.InvocationArguments;
16 import mockit.internal.state.TestRun;
17 import mockit.internal.util.GeneratedClasses;
18
19 final class PhasedExecutionState {
20 @NonNull
21 final List<Expectation> expectations;
22 @NonNull
23 final List<VerifiedExpectation> verifiedExpectations;
24 @NonNull
25 final EquivalentInstances equivalentInstances;
26 @NonNull
27 final InstanceBasedMatching instanceBasedMatching;
28 @Nullable
29 PartiallyMockedInstances partiallyMockedInstances;
30
31 PhasedExecutionState() {
32 expectations = new ArrayList<>();
33 verifiedExpectations = new ArrayList<>();
34 equivalentInstances = new EquivalentInstances();
35 instanceBasedMatching = new InstanceBasedMatching();
36 }
37
38 void addExpectation(@NonNull Expectation expectation) {
39 ExpectedInvocation invocation = expectation.invocation;
40 forceMatchingOnMockInstanceIfRequired(invocation);
41 removeMatchingExpectationsCreatedBefore(invocation);
42 expectations.add(expectation);
43 }
44
45 private void forceMatchingOnMockInstanceIfRequired(@NonNull ExpectedInvocation invocation) {
46 if (!invocation.matchInstance
47 && isToBeMatchedOnInstance(invocation.instance, invocation.getMethodNameAndDescription())) {
48 invocation.matchInstance = true;
49 }
50 }
51
52 boolean isToBeMatchedOnInstance(@Nullable Object mock, @NonNull String mockNameAndDesc) {
53 if (mock == null || mockNameAndDesc.charAt(0) == '<') {
54 return false;
55 }
56
57 if (instanceBasedMatching.isToBeMatchedOnInstance(mock)
58 || partiallyMockedInstances != null && partiallyMockedInstances.isToBeMatchedOnInstance(mock)) {
59 return true;
60 }
61
62 return TestRun.getExecutingTest().isInjectableMock(mock);
63 }
64
65 private void removeMatchingExpectationsCreatedBefore(@NonNull ExpectedInvocation invocation) {
66 Expectation previousExpectation = findPreviousExpectation(invocation);
67
68 if (previousExpectation != null) {
69 expectations.remove(previousExpectation);
70 invocation.copyDefaultReturnValue(previousExpectation.invocation);
71 }
72 }
73
74 @Nullable
75 private Expectation findPreviousExpectation(@NonNull ExpectedInvocation newInvocation) {
76 int n = expectations.size();
77
78 if (n == 0) {
79 return null;
80 }
81
82 Object mock = newInvocation.instance;
83 @NonNull
84 Boolean matchInstance = newInvocation.matchInstance;
85 String mockClassDesc = newInvocation.getClassDesc();
86 String mockNameAndDesc = newInvocation.getMethodNameAndDescription();
87 boolean isConstructor = newInvocation.isConstructor();
88
89 for (Expectation previous : expectations) {
90 if (isMatchingInvocation(mock, matchInstance, mockClassDesc, mockNameAndDesc, isConstructor, previous)
91 && isWithMatchingArguments(newInvocation, previous.invocation)) {
92 return previous;
93 }
94 }
95
96 return null;
97 }
98
99 private boolean isMatchingInvocation(@Nullable Object mock, @Nullable Boolean matchInstance,
100 @NonNull String mockClassDesc, @NonNull String mockNameAndDesc, boolean constructorInvocation,
101 @NonNull Expectation expectation) {
102 ExpectedInvocation invocation = expectation.invocation;
103
104 return invocation.isMatch(mock, mockClassDesc, mockNameAndDesc) && isSameMockedClass(mock, invocation.instance)
105 && (constructorInvocation || mock == null || isMatchingInstance(mock, matchInstance, expectation));
106 }
107
108 private static boolean isSameMockedClass(@Nullable Object mock1, @Nullable Object mock2) {
109 if (mock1 == mock2) {
110 return true;
111 }
112
113 if (mock1 != null && mock2 != null) {
114 Class<?> mockedClass1 = mock1.getClass();
115 Class<?> mockedClass2 = GeneratedClasses.getMockedClass(mock2);
116 return mockedClass2.isAssignableFrom(mockedClass1)
117 || TestRun.mockFixture().areCapturedClasses(mockedClass1, mockedClass2);
118 }
119
120 return false;
121 }
122
123 private boolean isWithMatchingArguments(@NonNull ExpectedInvocation newInvocation,
124 @NonNull ExpectedInvocation previousInvocation) {
125 InvocationArguments newArguments = newInvocation.arguments;
126 InvocationArguments previousArguments = previousInvocation.arguments;
127
128 if (newArguments.getMatchers() == null) {
129 return previousArguments.isMatch(newArguments.getValues(), equivalentInstances.instanceMap);
130 }
131
132 return newArguments.hasEquivalentMatchers(previousArguments);
133 }
134
135 @Nullable
136 Expectation findExpectation(@Nullable Object mock, @NonNull String mockClassDesc, @NonNull String mockNameAndDesc,
137 @NonNull Object[] args) {
138 boolean isConstructor = mockNameAndDesc.charAt(0) == '<';
139 Expectation replayExpectationFound = null;
140
141
142
143 for (Expectation expectation : expectations) {
144 if (replayExpectationFound != null && expectation.recordPhase == null) {
145 continue;
146 }
147
148 if (isMatchingInvocation(mock, null, mockClassDesc, mockNameAndDesc, isConstructor, expectation)
149 && expectation.invocation.arguments.isMatch(args, equivalentInstances.instanceMap)) {
150 if (expectation.recordPhase == null) {
151 replayExpectationFound = expectation;
152 continue;
153 }
154
155 if (isConstructor) {
156 equivalentInstances.registerReplacementInstanceIfApplicable(mock, expectation.invocation);
157 }
158
159 return expectation;
160 }
161 }
162
163 return replayExpectationFound;
164 }
165
166 private boolean isMatchingInstance(@NonNull Object invokedInstance, @Nullable Boolean matchInstance,
167 @NonNull Expectation expectation) {
168 ExpectedInvocation invocation = expectation.invocation;
169 Object invocationInstance = invocation.instance;
170 assert invocationInstance != null;
171
172 if (equivalentInstances.isEquivalentInstance(invocationInstance, invokedInstance)) {
173 return true;
174 }
175
176 if (TestRun.getExecutingTest().isInjectableMock(invokedInstance)
177 || partiallyMockedInstances != null
178 && partiallyMockedInstances.isDynamicMockInstanceOrClass(invokedInstance, invocationInstance)
179 || equivalentInstances.areNonEquivalentInstances(invocationInstance, invokedInstance)) {
180 return false;
181 }
182
183 return (matchInstance == null || !matchInstance) && !invocation.matchInstance && expectation.recordPhase != null
184 && !equivalentInstances.replacementMap.containsValue(invocationInstance);
185 }
186 }