New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GEODE-9897: Improve ClientUserAuthsTest #7206
GEODE-9897: Improve ClientUserAuthsTest #7206
Conversation
PROBLEM 1 In `ClientUserAuthsTest`, the `before()` method uses the `spy()` mechanism to override `getNextId()` on the `ClientUserAuths` instance it is testing. This bypasses the actual implementation for all tests, leaving the following important `ClientUserAuths` responsibilities untested: - Detect when all IDs have all been used. - Clear all existing authorizations when all IDs have been used, to force reauthentication. - Re-seed the ID generator when all IDs have been used. The tests are likely doing this because `getNextId()` relies on a `Random`, which is uncontrollable by design. Because `ClientUserAuths` creates the `Random`, the tests are unable to inject a testable instance, and so instead must bypass the `ClientUserAuths` methods that interact with the `Random`. PROBLEM 2 Some tests attempt to verify `ClientUserAuths` responsibilities only by observing how a `ClientUserAuths` interacts with itself. These tests verify only implementation details, and leave the actual responsibility untested. SOLUTION 1. (This commit.) Extract and test a `RandomSubjectIdGenerator` that generates random IDs. 2. (Subsequent commit in same PR.) Change existing tests so that they do not override production behavior, and so that they verify actual responsibilities. 3. (Subsequent PR.) Add tests for the now-testable responsibilities.
PROBLEM 1 In `ClientUserAuthsTest`, the `before()` method uses the `spy()` mechanism to override `getNextId()` on the `ClientUserAuths` instance it is testing. This bypasses the actual implementation for all tests, leaving the following important `ClientUserAuths` responsibilities untested: - Detect when all IDs have all been used. - Clear all existing authorizations when all IDs have been used, to force reauthentication. - Re-seed the ID generator when all IDs have been used. The tests are likely doing this because `getNextId()` relies on a `Random`, which is uncontrollable by design. Because `ClientUserAuths` creates the `Random`, the tests are unable to inject a testable instance, and so instead must bypass the `ClientUserAuths` methods that interact with the `Random`. PROBLEM 2 Some tests attempt to verify `ClientUserAuths` responsibilities only by observing how a `ClientUserAuths` interacts with itself. These tests verify only implementation details, and leave the actual responsibility untested. PROBLEM 3 `ClientUserAuths.putSubject(subject, id)` treats `id == 0` as a request to generate and assign an ID. The `getNextID()` method can generate ID 0, and so `putSubject()` might associate the subject with ID 0. If a caller later attempts to replace the subject by calling `putSubject(replacement, 0)`, `putSubject()` will generate a new ID and assign it to the replacement, rather than replacing the original subject. SOLUTION 1. (Previous commit in this PR.) Extract and test a `RandomSubjectIdGenerator` that generates random IDs. 2. (This PR.) Change existing tests so that they do not override production behavior, and so that they verify actual responsibilities. Update `getNextID()` to not generate ID 0. 3. (Subsequent PR.) Add tests for the now-testable responsibilities.
if (uniqueId == 0) { | ||
uniqueId = idGenerator.generateId(); | ||
} | ||
if (uniqueId == -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you would, you can refer to IDS_EXHAUSTED
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm going to leave this as -1
(but see below for an alternative). The reason is that there's already a weird, subtle coupling between the -1
here (where it means "IDs exhausted") and the -1
in putSubject()
(where it means "please generate an ID").
I say "weird" because, though -1
has a different meaning in each place, both places must use the same value. That's weird.
If I change this one to IDS_EXHAUSTED
, that hides the fact that we must use -1
in both places… which makes it no less weird, but even more subtle.
I'm not happy with the weirdness, the subtlety, or the coupling. But given that there's coupling and it's weird, I'm reluctant make it more subtle.
I considered making generateId()
return an OptionalLong
, which would eliminate the need for the generator to treat -1
as special in any way. I decided not to… out of a perhaps unfounded fear of allocating the OptionalLong
on the heap. Should I reconsider that?
private synchronized long getNextID() { | ||
long uniqueId = idGenerator.generateId(); | ||
if (uniqueId == 0) { | ||
uniqueId = idGenerator.generateId(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if idGenerator.generateId()
return 0
again? Chances are very slim though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The contract for an ID generator is to return unique IDs. If it can't do that, it must return -1 to indicate that it has exhausted all unique IDs. So a valid ID generator never returns the same ID twice (0 or otherwise) without first resetting itself and returning -1 to report ID exhaustion.
This contract is currently described only on the implementation of RandomSubjectIdGenerator
. I will add it to the SubjectIdGenerator
interface.
// now every user need to reauthenticate as we are short of Ids.. | ||
// though possibility of this is rare. | ||
uniqueIdVsUserAuth.clear(); | ||
return m_firstId; | ||
return idGenerator.generateId(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if idGenerator.generateId()
return -1
again? Chances are very slim though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "contract" for SubjectIdGenerator
is that it returns -1 only if it exhausts all available IDs. So the only way it will return -1 twice in a row only if it cannot generate any IDs in between the -1s.
I put "contract" in scare quotes because it's not written down anywhere. I should probably add a comment on SubjectIdGenerator
describing the contract.
Based on @jchen21's comments, I'm going to re-implement the generator to return |
PROBLEM 1
In
ClientUserAuthsTest
, thebefore()
method uses thespy()
mechanism to override
getNextId()
on theClientUserAuths
instance itis testing. This bypasses the actual implementation for all tests,
leaving the following important
ClientUserAuths
responsibilitiesuntested:
force reauthentication.
The tests are likely doing this because
getNextId()
relies on aRandom
, which is uncontrollable by design. BecauseClientUserAuths
creates the
Random
, the tests are unable to inject a testableinstance, and so instead must bypass the
ClientUserAuths
methods thatinteract with the
Random
.PROBLEM 2
Some tests attempt to verify
ClientUserAuths
responsibilities only byobserving how a
ClientUserAuths
interacts with itself. These testsverify only implementation details, and leave the actual responsibility
untested.
PROBLEM 3
ClientUserAuths.putSubject(subject, id)
treatsid == 0
as a requestto generate and assign an ID. The
getNextID()
method can generate ID0, and so
putSubject()
might associate the subject with ID 0. If acaller later attempts to replace the subject by calling
putSubject(replacement, 0)
,putSubject()
will generate a new ID andassign it to the replacement, rather than replacing the original
subject.
SOLUTION
RandomSubjectIdGenerator
that generates random IDs.getNextID()
to not generate ID 0.