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
Prevent NullPointerException if any of the signing properties is null but signing isn't required #2268
Prevent NullPointerException if any of the signing properties is null but signing isn't required #2268
Conversation
74ab1ca
to
4d40f11
Compare
Hi @colindean. Thanks for the PR. Could you please confirm whether you've signed a Gradle CLA? |
I previously signed one with a previous employer, but I've just now signed it for myself, as whom this contribution was made. |
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.
Thanks for providing the pull request. Could you please address the review comments.
- Can you please also add an integration test that demonstrates the change of behavior.
- Maybe also add a note to the user guide that null values for the properties will throw an exception.
if(prop != null){ | ||
values.add(prop); | ||
} else { | ||
if(required){ |
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.
This logic is duplicated and should be reused. The else
block below it uses the same logic.
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 exception messages differ. Another concern was keeping the C/M/L on the exceptions thrown therein as close to the source of the problem as possible. If I extract this to a method like this:
private boolean throwIfRequired(boolean required, String message)
then I'll have to check the return value of the method in order to know if I should return early from the readProperties
method.
I'm drawing a blank presently for ways to accomplish this more cleanly in Java.
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 made an attempt at refactoring in 1019328. It reads clearer but exposes some sentiment I captured in the commit message: this capability to retrieve a property safely probably belongs to Project as an overloaded method.
class PgpSignatoryFactorySpec extends Specification { | ||
|
||
//https://github.com/gradle/gradle/issues/2267 | ||
protected Project aProjectWithNullProperties(){ |
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 method can just be static
and without protected
.
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.
✅ in 13032f2
|
||
class PgpSignatoryFactorySpec extends Specification { | ||
|
||
//https://github.com/gradle/gradle/issues/2267 |
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.
Should use @Issue
annotation to indicate the issue URL. The annotation should be added on the class level.
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.
✅ in 9c421c7
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.
Thanks for the pointer. I wasn't aware that we still had other tests with the Spec
suffix. We'll likely migrate them to Test
at a later time.
import org.gradle.testfixtures.ProjectBuilder | ||
import spock.lang.* | ||
|
||
class PgpSignatoryFactorySpec extends Specification { |
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.
In the code base we mainly follow the convention Test
for test class names instead of Spec
.
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.
org.gradle.plugins.signing.signatory.pgp.PgpKeyIdSpec
was my reference for creating the test.
✅ in 346af98
Exception ex = thrown() | ||
ex.class != NullPointerException | ||
} | ||
def "null properties throw a nice exception"() { |
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.
We should test this for each of the properties. At the moment it will always fail on the first property. Let's use a where
clause here + @Unroll
. Let's also change "nice" into "human-readable" or "descriptive".
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.
Descriptive
✅ in ece23b8
- Where clause with
@Unroll
. I'll have to research how to do what you're asking.
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.
OK, you'll find plenty of examples in the Gradle core code base.
when: | ||
factory.createSignatory(project, true) | ||
then: | ||
thrown InvalidUserDataException |
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.
Should verify the exception message.
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.
✅ in 4eba1fb
when: | ||
factory.createSignatory(project, true) | ||
then: | ||
//notThrown NullPointerException // does not allow the real exception through |
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.
This should rather be noExceptionThrown()
.
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.
It's OK if an exception is thrown here, but we want it not to be an NPE. Wouldn't noExceptionThrown()
cause all exceptions to fail the test?
If a property such as `signing.keyId` was set to a null value, perhaps by setting it to the return of `System.getenv('KEY_ID')` when `KEY_ID` is unset, the PgpSignatoryFactory would throw an NPE when reading those properties while constructing the PgpSignatory. This change adds a null check that also bypasses signatory creation when `required` is false. Fixes gradle#2267
4d40f11
to
4eba1fb
Compare
I feel like this should the responsibility of Project. A consumer should be able to require that a property be present and not null and get exceptions when the conditions are unmet. From https://github.com/gradle/gradle/pull/2268/files#r130179732
Can you recommend an example integration test that I can use as inspiration for the right way to do it?
Nevermind, my search string was too long for what was in TODO:
|
Have a look at |
when: | ||
factory.createSignatory(project, true) | ||
then: | ||
Exception ex = thrown() |
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.
Should use thrown(InvalidUserDataException)
.
when: | ||
factory.createSignatory(project, true) | ||
then: | ||
//notThrown NullPointerException // does not allow the real exception through |
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 we should check the actual exception type here. In fact I think we can delete this test case as it is covered by null properties throw a descriptive exception
. Please also see my comment below on asserting the correct exception.
import org.gradle.api.InvalidUserDataException | ||
import org.gradle.api.Project | ||
import org.gradle.testfixtures.ProjectBuilder | ||
import spock.lang.* |
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.
Can you run "optimize imports" from the IDE here?
@colindean Thanks again for your fixes. I am going to finish off the PR. No more need for any changes from your end. |
Thank you! I would have liked to do more but I'm a little time limited. I was working on this during some funemployment! |
If a property such as
signing.keyId
was set to a null value, perhaps by setting it to the return ofSystem.getenv('KEY_ID')
whenKEY_ID
is unset, the PgpSignatoryFactory would throw an NPE when reading those properties while constructing the PgpSignatory.This change adds a null check that also bypasses signatory creation when
required
is false.Fixes #2267