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

Relax expectation for unspecified errors, fix expectation for cardinality violations #833

Merged
merged 7 commits into from
Mar 18, 2024

Conversation

jhump
Copy link
Member

@jhump jhump commented Mar 15, 2024

There are numerous error conditions in gRPC where the actual behavior of a client or server in the face of such an error is unspecified. In particular, there is no specification for how a client or server should react to an invalid/unexpected HTTP method or content-type. So this adds a mechanism to supply additional acceptable error codes.

Instead of just allowing any error for these unspecified cases, I made a judgement call as to what the other acceptable codes are. While the exact error is not specified, it is pretty clear (at least IMO) that most of the codes do not apply at all and should not be used. We can always relax this further by adding more allowed codes. Though we don't want to accept any code because the docs on gRPC codes does explicitly prohibit a framework from using certain codes -- codes that are reserved for use by actual application logic (see the table at the very bottom of the page linked above).

One that thing that is specified, but was codified with the wrong expectation in these tests, is related to cardinality violations. A cardinality violation is when exactly one request or response is expected (for a unary RPC or the non-streaming half of a non-bidi streaming RPC), but there is actually zero or more than one. This is specified in a table that accompanies the docs for gRPC codes (follow that link and search for "cardinality violation" in that page).

Sadly, neither connect-go nor grpc-go correctly handle the cardinality violations per the table in the above doc. So correcting these cases (and adding more) means we now have numerous more test cases configured as "known failing" for all implementations. We will address all of these issues in connect-go in the next release, so when we get that done and pulled into this repo, we can remove the known failing cases for the reference implementations.

…ified; fix expectation for cardinality violations to match spec; add cardinality violation tests for client-stream
@jhump
Copy link
Member Author

jhump commented Mar 15, 2024

cc @jchadwick-buf, @drice-buf

@jhump jhump requested a review from smaye81 March 15, 2024 21:43
errs = append(errs, fmt.Errorf("actual error code %d (%s) does not match expected code %d (%s)",
actual.Code, connect.Code(actual.Code).String(), expected.Code, connect.Code(expected.Code).String()))
if expected.Code != actual.Code && !inSlice(actual.Code, otherCodes) {
expectedCode := fmt.Sprintf("%d (%s)", expected.Code, connect.Code(expected.Code).String())

Choose a reason for hiding this comment

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

Could avoid initializing expectedCode for the case where len(otherCodes) > 0

Copy link
Member Author

Choose a reason for hiding this comment

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

But we actually use this initialized value in that case, at line 478 below. So it seemed simplest to me to construct it once instead of having to do it 2x from both the if and else cases.

Copy link

@drice-buf drice-buf Mar 18, 2024

Choose a reason for hiding this comment

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

Thanks, didn't catch that usage. I have a feeling this could be done a bit more elegantly (maybe looping over append(expected, otherCodes...) and avoiding the need to use or in the phrasing. But it's fine as-is.

Copy link
Member Author

Choose a reason for hiding this comment

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

I reformulated, and also added a test to make sure it comes up with the expected string to present to users. Better?

Choose a reason for hiding this comment

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

Looks great!

@@ -459,7 +459,7 @@ func checkPayloads(expected, actual []*conformancev1.ConformancePayload) multiEr
return errs
}

func checkError(expected, actual *conformancev1.Error) multiErrors {
func checkError(expected, actual *conformancev1.Error, otherCodes []conformancev1.Code) multiErrors {

Choose a reason for hiding this comment

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

Would it make sense to use a map[string]struct{} or other Set type here to make searching easier?

Copy link
Member Author

Choose a reason for hiding this comment

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

The list of codes is always small, so linear scan seemed fine (likely faster than having to create a map). As far as just simplifying the code (since perf is not a big concern here), I think constructing the map from the repeated proto field would be the same amount of code as the linear search through the slice, so didn't seem like a clear win. Also, the linear search is part of the standard library in Go 1.21, which introduces a new slices package, so we could upgrade in the near future and then remove some of this code.

I can add a comment to the inSlice function about removing it once we update to Go 1.21.

@@ -141,4 +141,6 @@ message TestCase {
// Specifying an expected response explicitly in test definitions will override
// the auto-generation of the test runner.
ClientResponseResult expected_response = 3;

repeated Code other_allowed_error_codes = 4;

Choose a reason for hiding this comment

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

Add a comment

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, very good point. And I also need to update the docs on authoring test cases to refer to this value and provide context on when to use it.

return false
}

func expectedCodeString(expectedCode conformancev1.Code, otherAllowedCodes []conformancev1.Code) string {

Choose a reason for hiding this comment

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

Nice

@jhump jhump merged commit 3f31e07 into main Mar 18, 2024
7 checks passed
@jhump jhump deleted the jh/relax-expectation-for-unspecified-errors branch March 18, 2024 17:39
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.

None yet

3 participants