• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / jmockit1 / 533

26 Nov 2025 03:18PM UTC coverage: 72.291% (+0.09%) from 72.2%
533

Pull #422

github

web-flow
Merge 447b13428 into f3aed3cfe
Pull Request #422: Restore @Mock invocation constraints and fix MockedTypeCascade concurrency

5732 of 8416 branches covered (68.11%)

Branch coverage included in aggregate %.

78 of 98 new or added lines in 6 files covered. (79.59%)

6 existing lines in 1 file now uncovered.

11998 of 16110 relevant lines covered (74.48%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.53
/main/src/main/java/mockit/internal/faking/FakeState.java
1
/*
2
 * MIT License
3
 * Copyright (c) 2006-2025 JMockit developers
4
 * See LICENSE file for full license text.
5
 */
6
package mockit.internal.faking;
7

8
import edu.umd.cs.findbugs.annotations.NonNull;
9
import edu.umd.cs.findbugs.annotations.Nullable;
10

11
import java.lang.reflect.Member;
12
import java.lang.reflect.Method;
13

14
import mockit.internal.expectations.invocation.MissingInvocation;
15
import mockit.internal.expectations.invocation.UnexpectedInvocation;
16
import mockit.internal.faking.FakeMethods.FakeMethod;
17
import mockit.internal.reflection.MethodReflection;
18
import mockit.internal.reflection.RealMethodOrConstructor;
19
import mockit.internal.util.ClassLoad;
20

21
final class FakeState {
22
    private static final ClassLoader THIS_CL = FakeState.class.getClassLoader();
1✔
23

24
    @NonNull
25
    final FakeMethod fakeMethod;
26
    @Nullable
27
    private Method actualFakeMethod;
28
    @Nullable
29
    private Member realMethodOrConstructor;
30
    @Nullable
31
    private Object realClass;
32

33
    // Constraints pulled from the @Mock annotation; negative values indicate "no constraint".
34
    private int expectedInvocations;
35
    private int minExpectedInvocations;
36
    private int maxExpectedInvocations;
37

38
    // Current fake invocation state:
39
    private int invocationCount;
40
    @Nullable
41
    private ThreadLocal<FakeInvocation> proceedingInvocation;
42

43
    // Helper field just for synchronization:
44
    @NonNull
45
    private final Object invocationCountLock;
46

47
    FakeState(@NonNull FakeMethod fakeMethod) {
1✔
48
        this.fakeMethod = fakeMethod;
1✔
49
        invocationCountLock = new Object();
1✔
50
        expectedInvocations = -1;
1✔
51
        minExpectedInvocations = 0;
1✔
52
        maxExpectedInvocations = -1;
1✔
53

54
        if (fakeMethod.canBeReentered()) {
1✔
55
            makeReentrant();
1✔
56
        }
57
    }
1✔
58

59
    FakeState(@NonNull FakeState fakeState) {
×
60
        fakeMethod = fakeState.fakeMethod;
×
61
        actualFakeMethod = fakeState.actualFakeMethod;
×
62
        realMethodOrConstructor = fakeState.realMethodOrConstructor;
×
63
        invocationCountLock = new Object();
×
NEW
64
        realClass = fakeState.realClass;
×
NEW
65
        invocationCount = fakeState.invocationCount;
×
NEW
66
        expectedInvocations = fakeState.expectedInvocations;
×
NEW
67
        minExpectedInvocations = fakeState.minExpectedInvocations;
×
NEW
68
        maxExpectedInvocations = fakeState.maxExpectedInvocations;
×
69

70
        if (fakeState.proceedingInvocation != null) {
×
71
            makeReentrant();
×
72
        }
73
    }
×
74

75
    @NonNull
76
    Class<?> getRealClass() {
77
        return fakeMethod.getRealClass();
1✔
78
    }
79

80
    private void makeReentrant() {
81
        proceedingInvocation = new ThreadLocal<>();
1✔
82
    }
1✔
83

84
    boolean isWithExpectations() {
85
        return expectedInvocations >= 0 || minExpectedInvocations > 0 || maxExpectedInvocations >= 0;
1✔
86
    }
87

88
    void setExpectedInvocations(int expectedInvocations) {
89
        this.expectedInvocations = expectedInvocations;
1✔
90
    }
1✔
91

92
    void setMinExpectedInvocations(int minExpectedInvocations) {
93
        this.minExpectedInvocations = minExpectedInvocations;
1✔
94
    }
1✔
95

96
    void setMaxExpectedInvocations(int maxExpectedInvocations) {
97
        this.maxExpectedInvocations = maxExpectedInvocations;
1✔
98
    }
1✔
99

100
    boolean update() {
101
        if (proceedingInvocation != null) {
1✔
102
            FakeInvocation invocation = proceedingInvocation.get();
1✔
103

104
            if (invocation != null && invocation.proceeding) {
1✔
105
                invocation.proceeding = false;
1✔
106
                return false;
1✔
107
            }
108
        }
109

110
        int timesInvoked;
111

112
        synchronized (invocationCountLock) {
1✔
113
            timesInvoked = ++invocationCount;
1✔
114
        }
1✔
115

116
        verifyUnexpectedInvocation(timesInvoked);
1✔
117

118
        return true;
1✔
119
    }
120

121
    private void verifyUnexpectedInvocation(int timesInvoked) {
122
        if (expectedInvocations >= 0 && timesInvoked > expectedInvocations) {
1✔
123
            throw new UnexpectedInvocation(fakeMethod.errorMessage("exactly", expectedInvocations, timesInvoked));
1✔
124
        }
125

126
        if (maxExpectedInvocations >= 0 && timesInvoked > maxExpectedInvocations) {
1✔
127
            throw new UnexpectedInvocation(fakeMethod.errorMessage("at most", maxExpectedInvocations, timesInvoked));
1✔
128
        }
129
    }
1✔
130

131
    void verifyMissingInvocations() {
132
        int timesInvoked = getTimesInvoked();
1✔
133

134
        if (expectedInvocations >= 0 && timesInvoked < expectedInvocations) {
1!
135
            throw new MissingInvocation(fakeMethod.errorMessage("exactly", expectedInvocations, timesInvoked));
1✔
136
        }
137

138
        if (minExpectedInvocations > 0 && timesInvoked < minExpectedInvocations) {
1!
139
            throw new MissingInvocation(fakeMethod.errorMessage("at least", minExpectedInvocations, timesInvoked));
1✔
140
        }
NEW
141
    }
×
142

143
    int getTimesInvoked() {
144
        synchronized (invocationCountLock) {
1✔
145
            return invocationCount;
1✔
146
        }
147
    }
148

149
    void reset() {
NEW
150
        synchronized (invocationCountLock) {
×
NEW
151
            invocationCount = 0;
×
NEW
152
        }
×
NEW
153
    }
×
154

155
    @NonNull
156
    Member getRealMethodOrConstructor(@NonNull String fakedClassDesc, @NonNull String fakedMethodName,
157
            @NonNull String fakedMethodDesc) {
158
        Class<?> fakedClass = ClassLoad.loadFromLoader(THIS_CL, fakedClassDesc.replace('/', '.'));
1✔
159
        return getRealMethodOrConstructor(fakedClass, fakedMethodName, fakedMethodDesc);
1✔
160
    }
161

162
    @NonNull
163
    Member getRealMethodOrConstructor(@NonNull Class<?> fakedClass, @NonNull String fakedMethodName,
164
            @NonNull String fakedMethodDesc) {
165
        Member member = realMethodOrConstructor;
1✔
166

167
        if (member == null || !fakedClass.equals(realClass)) {
1✔
168
            String memberName = "$init".equals(fakedMethodName) ? "<init>" : fakedMethodName;
1✔
169

170
            RealMethodOrConstructor realMember;
171
            try {
172
                realMember = new RealMethodOrConstructor(fakedClass, memberName, fakedMethodDesc);
1✔
173
            } catch (NoSuchMethodException e) {
×
174
                throw new RuntimeException(e);
×
175
            }
1✔
176

177
            member = realMember.getMember();
1✔
178

179
            if (!fakeMethod.isAdvice) {
1✔
180
                realMethodOrConstructor = member;
1✔
181
                realClass = fakedClass;
1✔
182
            }
183
        }
184

185
        return member;
1✔
186
    }
187

188
    boolean shouldProceedIntoRealImplementation(@Nullable Object fake, @NonNull String classDesc) {
189
        if (proceedingInvocation != null) {
1✔
190
            FakeInvocation pendingInvocation = proceedingInvocation.get();
1✔
191

192
            // noinspection RedundantIfStatement
193
            if (pendingInvocation != null && pendingInvocation.isMethodInSuperclass(fake, classDesc)) {
1✔
194
                return true;
1✔
195
            }
196
        }
197

198
        return false;
1✔
199
    }
200

201
    void prepareToProceed(@NonNull FakeInvocation invocation) {
202
        if (proceedingInvocation == null) {
1✔
203
            throw new UnsupportedOperationException("Cannot proceed into abstract/interface method");
1✔
204
        }
205

206
        if (fakeMethod.isForNativeMethod()) {
1✔
207
            throw new UnsupportedOperationException("Cannot proceed into real implementation of native method");
1✔
208
        }
209

210
        FakeInvocation previousInvocation = proceedingInvocation.get();
1✔
211

212
        if (previousInvocation != null) {
1✔
213
            invocation.setPrevious(previousInvocation);
1✔
214
        }
215

216
        proceedingInvocation.set(invocation);
1✔
217
    }
1✔
218

219
    void prepareToProceedFromNonRecursiveFake(@NonNull FakeInvocation invocation) {
220
        assert proceedingInvocation != null;
1!
221
        proceedingInvocation.set(invocation);
1✔
222
    }
1✔
223

224
    void clearProceedIndicator() {
225
        assert proceedingInvocation != null;
1!
226
        FakeInvocation currentInvocation = proceedingInvocation.get();
1✔
227
        FakeInvocation previousInvocation = (FakeInvocation) currentInvocation.getPrevious();
1✔
228
        proceedingInvocation.set(previousInvocation);
1✔
229
    }
1✔
230

231
    @NonNull
232
    Method getFakeMethod(@NonNull Class<?> fakeClass, @NonNull Class<?>[] parameterTypes) {
233
        if (actualFakeMethod == null) {
1✔
234
            actualFakeMethod = MethodReflection.findCompatibleMethod(fakeClass, fakeMethod.name, parameterTypes);
1✔
235
        }
236

237
        return actualFakeMethod;
1✔
238
    }
239
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc