Skip to content
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

Add protobuf conformance tests v1 #2404

Merged

Conversation

Dogacel
Copy link
Contributor

@Dogacel Dogacel commented Aug 7, 2023

Includes the message type used to run conformance tests by Google.

My intention is to catch and demonstrate some serialization errors & missing features. Commented parts are proven to be non-working. Probably some tests can be improved by using generators and multiple iterations with edge cases.

A part of

@Dogacel
Copy link
Contributor Author

Dogacel commented Aug 12, 2023

Hi team, would be glad if some review can be done, I am hoping those bugs will be resolved and protobuf module can hopefully promote to stable after all tests are fixed 🙏

I am willing to help whenever I am free.

@shanshin
Copy link
Contributor

Hi,
sorry for the delay and thanks for the PR!
We will review it in the next few days.

Comment on lines 62 to 68
if (restored.hasOneofString()) assertEquals(message.oneofString, restored.oneofString)
if (restored.hasOneofBytes()) assertContentEquals(message.oneofBytes, restored.oneofBytes.toByteArray())
if (restored.hasOneofBool()) assertEquals(message.oneofBool, restored.oneofBool)
if (restored.hasOneofUint64()) assertEquals(message.oneofUint64, restored.oneofUint64.toULong())
if (restored.hasOneofFloat()) assertEquals(message.oneofFloat, restored.oneofFloat)
if (restored.hasOneofDouble()) assertEquals(message.oneofDouble, restored.oneofDouble)
if (restored.hasOneofEnum()) assertEquals(message.oneofEnum?.name, restored.oneofEnum?.name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than checking restored, you should probably check message

@sandwwraith
Copy link
Member

Just for the clarification of your intentions — do you want this PR to be merged with commented code? Or you want to leave it as a showcase until all issues are fixed?

@Dogacel
Copy link
Contributor Author

Dogacel commented Aug 15, 2023

Just for the clarification of your intentions — do you want this PR to be merged with commented code? Or you want to leave it as a showcase until all issues are fixed?

Hi,

My personal preference is to merge those tests, either with comments or mark them as @Ignored (which causes some duplicate code that's why I choose to comment). My intention to add them to tests was to showcase what's missing in the code and also make testing easier for developers.

As library maintainers, please let me know how you want to merge this. If you are against keeping comments, I think it is fine to remove them and open issues in GitHub to track. But I think we should merge the non-commented tests anyway because conformance tests are an important indicator to verify functionality.

@sandwwraith
Copy link
Member

I see.

But I think we should merge the non-commented tests anyway because conformance tests are an important indicator to verify functionality.

Yes, this is a great point. I suggest to do the following:

  1. Carefully split test data to working and non-working parts (latter in a separate file). It looks to me that amount of duplicated code won't be that big. Non-working parts should be @Ignored, not commented.
  2. Create an issue(s) (if there's not one already) that describes missing parts. Issue number should be added as a comment to @Ignore annotation. You can create one big issue or several separate, whatever suits you best.

Does that sound plausible to you?

@Dogacel
Copy link
Contributor Author

Dogacel commented Aug 20, 2023

I see.

But I think we should merge the non-commented tests anyway because conformance tests are an important indicator to verify functionality.

Yes, this is a great point. I suggest to do the following:

  1. Carefully split test data to working and non-working parts (latter in a separate file). It looks to me that amount of duplicated code won't be that big. Non-working parts should be @Ignored, not commented.
  2. Create an issue(s) (if there's not one already) that describes missing parts. Issue number should be added as a comment to @Ignore annotation. You can create one big issue or several separate, whatever suits you best.

Does that sound plausible to you?

Hi,

Yep that sounds plausible. I am currently traveling and also sick so will complete this when I have time and feel better 😆

@sandwwraith
Copy link
Member

No problems, take your time and get well!

Copy link
Contributor

@pdvrieze pdvrieze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it looks ok to me (not an official dev). I'd say split the individual tests off into separate functions per type. Otherwise I find the code containing a lot of repetitive elements that don't help to make the code clear. Given that the types are local to the tests the API itself isn't really an issue, but perhaps the resulting verbosity isn't ideal (I don't really know how to solve the clarity issue though without adding just as much complexity in other places).

Comment on lines 43 to 54
fun default() {
listOf(
KTestMessageProto3Oneof(oneofUint32 = 150u),
KTestMessageProto3Oneof(oneofNestedMessage = KTestMessagesProto3Message.KNestedMessage(a = 150)),
KTestMessageProto3Oneof(oneofString = "150"),
KTestMessageProto3Oneof(oneofBytes = "150".toByteArray()),
KTestMessageProto3Oneof(oneofBool = true),
KTestMessageProto3Oneof(oneofUint64 = 150uL),
KTestMessageProto3Oneof(oneofFloat = 150f),
KTestMessageProto3Oneof(oneofDouble = 150.0),
KTestMessageProto3Oneof(oneofEnum = KTestMessagesProto3Enum.KNestedEnum.BAR),
).forEach { message ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than testing all types in a single function it would make sense to have the inner loop in a separate function and just have separate test functions for the different data members. This would help with testing individual types, and brings more visibility (each actual test function would just call the actual implementation with the message as parameter). It would also allow testing each type even in case of some failure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with that. This would allow to determine what exact instance of KTestMessageProto3Oneof caused failure.

Or, If you prefer a different way, you can add an assertion message to each assertion, but this looks much more boilerplate-ish.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to reduce boilerplate by creating some verification functions. Let me know how it looks now.

Comment on lines +55 to +68
assertEquals(message.optionalInt32, restored.optionalInt32)
assertEquals(message.optionalInt64, restored.optionalInt64)
assertEquals(message.optionalUint32, restored.optionalUint32.toUInt())
assertEquals(message.optionalUint64, restored.optionalUint64.toULong())
assertEquals(message.optionalSint32, restored.optionalSint32)
assertEquals(message.optionalSint64, restored.optionalSint64)
assertEquals(message.optionalFixed32, restored.optionalFixed32)
assertEquals(message.optionalFixed64, restored.optionalFixed64)
assertEquals(message.optionalSfixed32, restored.optionalSfixed32)
assertEquals(message.optionalSfixed64, restored.optionalSfixed64)
assertEquals(message.optionalFloat, restored.optionalFloat)
assertEquals(message.optionalDouble, restored.optionalDouble)
assertEquals(message.optionalBool, restored.optionalBool)
assertEquals(message.optionalString, restored.optionalString)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you are already comparing the full message below these comparisons seem double.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have compared the fields with the serialized one rather than the restored.

Sometimes message can be serialized and deserialized it might be the same but the serialization format might be different so if deserialized by another library it can be something else. This is most obvious with signed ints (i.e. it appears as 2*int - 1)

My intention was to catch this serialized format check.

assertContentEquals(message.optionalBytes, restored.optionalBytes.toByteArray())

val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Primitive>(restored.toByteArray())
assertEquals(message, restoredMessage.copy(optionalBytes = message.optionalBytes))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may add a comment on what you are doing here (compensating for the fact that you didn't provide an equals implementation for the message type).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a comment would be nice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, added where I do this trick.

Copy link
Member

@sandwwraith sandwwraith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Just fix the minor issues I've mentioned, and we can merge this.

assertContentEquals(message.optionalBytes, restored.optionalBytes.toByteArray())

val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Primitive>(restored.toByteArray())
assertEquals(message, restoredMessage.copy(optionalBytes = message.optionalBytes))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a comment would be nice

Comment on lines 43 to 54
fun default() {
listOf(
KTestMessageProto3Oneof(oneofUint32 = 150u),
KTestMessageProto3Oneof(oneofNestedMessage = KTestMessagesProto3Message.KNestedMessage(a = 150)),
KTestMessageProto3Oneof(oneofString = "150"),
KTestMessageProto3Oneof(oneofBytes = "150".toByteArray()),
KTestMessageProto3Oneof(oneofBool = true),
KTestMessageProto3Oneof(oneofUint64 = 150uL),
KTestMessageProto3Oneof(oneofFloat = 150f),
KTestMessageProto3Oneof(oneofDouble = 150.0),
KTestMessageProto3Oneof(oneofEnum = KTestMessagesProto3Enum.KNestedEnum.BAR),
).forEach { message ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with that. This would allow to determine what exact instance of KTestMessageProto3Oneof caused failure.

Or, If you prefer a different way, you can add an assertion message to each assertion, but this looks much more boilerplate-ish.

@Dogacel
Copy link
Contributor Author

Dogacel commented Sep 23, 2023

Looks great! Just fix the minor issues I've mentioned, and we can merge this.

Thanks. It took me a bit of time to catch-up but fixed some stuff and ready for another round of review. Thanks!

Copy link
Member

@sandwwraith sandwwraith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing job, thank you very much!

@sandwwraith sandwwraith merged commit a675cb3 into Kotlin:dev Oct 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants