Skip to content

Protocol tests parsed response validation #3414

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

Merged
merged 4 commits into from
May 22, 2025
Merged

Conversation

SergeyRyabinin
Copy link
Contributor

@SergeyRyabinin SergeyRyabinin commented May 8, 2025

Issue #, if available:
Protocol tests
Description of changes:
Generate parsed response structure validation
Check all that applies:

  • Did a review by yourself.
  • Added proper tests to cover this PR. (If tests are not applicable, explain.)
  • Checked if this PR is a breaking (APIs have been changed) change.
  • Checked if this PR will not introduce cross-platform inconsistent behavior.
  • Checked if this PR would require a ReadMe/Wiki update.

Check which platforms you have built SDK on to verify the correctness of this PR.

  • Linux
  • Windows
  • Android
  • MacOS
  • IOS
  • Other Platforms

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

else: # Unix-like
self.process.send_signal(signal.SIGINT)

time.sleep(0.5)
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need static sleep? what are we waiting for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we need to wait a bit to let the process and OS to terminate the process.

def wait(self):
"""Wait for the subprocess to complete."""
if self.process:
return self.process.wait()
Copy link
Contributor

Choose a reason for hiding this comment

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

waits without timeouts have heavily traumatized my up to this point, lets add a reasonable timeout to prevent zombie hosts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is actually a left-over from the AI generated slop I used as an initial draft.
I just removed it.

.collect(Collectors.toSet());
// include the request shapes
Set<String> requestShapes = testSuite.getCases().stream()
.map(entry -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like we only ever add one item to the set ever, simplified this could just be

testSuite.getCases().stream()
  .map(entry -> { return String.format("<aws/%s/model/%s.h>", 
      serviceModel.getMetadata().getProjectName(),
      entry.getGiven().getName() + "Request")})
  .collect(Collectors.toSet());

to avoid creating a extra set per test case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

copy-paste mistake, thank you!

#if(!$expectedResult.isEmpty())
const ${responseShape.name}& result = outcome.GetResult();
#end
/* expectedResult = R"( ${expectedResult} )" */
Copy link
Contributor

Choose a reason for hiding this comment

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

love the debug comment

/**
* Perform legacy patching of the ec2 model present from the very beginning.
*/
private void legacyPatchEc2Model() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can be optimized to use one loop

private void legacyPatchEc2Model() {
    if (!"ec2".equals(serviceModel.getMetadata().getProtocol())) {
        return;
    }

    Map<String, Shape> shapes = serviceModel.getShapes();
    Map<String, Shape> updatedShapes = new HashMap<>();

    for (Map.Entry<String, Shape> entry : shapes.entrySet()) {
        String key = entry.getKey();
        Shape shape = entry.getValue();

        String newKey = key.replaceAll("Result$", "Response");
        shape.setName(shape.getName().replaceAll("Result$", "Response"));
        shape.setType(shape.getType().replaceAll("Result$", "Response"));

        updatedShapes.put(newKey, shape);
    }

    shapes.clear();
    shapes.putAll(updatedShapes);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The complexity is the same, in addition, this was a copy-pasted method from the actual EC2 client generator.
I refactored it a bit and re-using the existing one.

Additionally,

Map<String, Shape> updatedShapes = new HashMap<>();

would re-shuffle shapes in a map as far as I remember, Json parsers use Linked structures.

print(f"No protocol tests found in {self.build_dir}")
self.fail = True
for test in tests:
mock_server = MockHttpServerHandle()
Copy link
Contributor

Choose a reason for hiding this comment

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

should be used with 'with' for cleanup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thank you, I've refactored this piece with enter/exit methods and with.

"""

def __init__(self, args):
self.debug = args.get("debug", False)
Copy link
Contributor

Choose a reason for hiding this comment

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

unused?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, unused.
Thank you for noticing!

self.process = subprocess.Popen(python_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(f"Started mock server with PID {self.process.pid}")

def __del__(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

This may not be deterministically called by the interpreter per https://docs.python.org/3.4/reference/datamodel.html
Better to use https://book.pythontips.com/en/latest/context_managers.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you, I've refactored the code to explicitly implicitly call new method stop() with the with statement.

"""

def __init__(self):
python_cmd = [shutil.which("python3"), PROTO_TEST_MOCK_HANDLER]
Copy link
Contributor

Choose a reason for hiding this comment

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

No exception handling found

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've added a bit of exception handling here, but can't do much more than that.
Catch-trace-crash/return -1 is the current approach.

mock_server = MockHttpServerHandle()
try:
process = subprocess.run([test], timeout=6 * 60, check=True)
process.check_returncode()
Copy link
Contributor

Choose a reason for hiding this comment

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

this is redundant as check is True
per python documentation
If check is true, and the process exits with a non-zero exit code, a [CalledProcessError](https://docs.python.org/3/library/subprocess.html#subprocess.CalledProcessError) exception will be raised. Attributes of that exception hold the arguments, the exit code, and stdout and stderr if they were captured.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you, I removed the check.

:return: dict with parsed arguments
"""
parser = argparse.ArgumentParser(description="Protocol tests driver for AWS-SDK-CPP")
parser.add_argument("--debug", action="store_true", help="Print additional information")
Copy link
Contributor

Choose a reason for hiding this comment

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

tiny suggestion: parser.add_argument("--debug", action="store_true", default = False, help="Print additional information")

and remove logic later


def __init__(self):
try:
python_cmd = [shutil.which("python3"), PROTO_TEST_MOCK_HANDLER]
Copy link
Contributor

Choose a reason for hiding this comment

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

sys.executable if you want to make sure its the same python?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No need to be sure it is the same interpreter

  • test driver
  • mock server
  • test app
    All can be seen as an independent apps.

@sbera87 sbera87 self-requested a review May 22, 2025 16:47
@SergeyRyabinin SergeyRyabinin merged commit 9225f08 into main May 22, 2025
4 checks passed
@SergeyRyabinin SergeyRyabinin deleted the sr/pt/response branch May 22, 2025 17:15
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