-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
feat(integ-tests): enhancements to integ-tests #20180
feat(integ-tests): enhancements to integ-tests #20180
Conversation
This PR contains various enhancements including - API ergonomics improvements - renamed `queryAws` to `awsApiCall` - Now using `Match` from @aws-cdk/assertions for the assertions provider - `DeployAssert` now creates its own stack - Additional assertion types (OBJECT_LIKE) - utility for invoking lambda functions (separate from `awsApiCall`)
}, | ||
responsePayload: 'success', | ||
}), | ||
assertionType: AssertionType.OBJECT_LIKE, |
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 we not do ExpectedResult.objectLike()
instead of this?
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.
Combining what you said in the previous comment maybe I'll add another method so you could do
message.assertObjectLikeAtPath('Messages.0.Body', {
...,
});
Or we could reduce the methods down and put the type of assertion on ExpectedResult
? Something like
message.assert(ExpectedResult.objectLike());
message.assert(ExpectedResult.objectEquals());
message.assertAtPath('Messages.0.Body', ExpectedResult.stringEquals());
I think ideally you would not need to use the EqualsAssertion
class directly.
WaitTimeSeconds: 20, | ||
}); | ||
|
||
new EqualsAssertion(integ.assert, 'ReceiveMessage', { |
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.
Helper function on message
for this?
message.assertEquals(ExpectedResult.fromObject({
// ...
}));
?
Or if we need to select a subobject:
message.atPath('Message.0.Body')
}); | ||
|
||
// assert the results | ||
describe.assertObjectLike({ |
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.
Oh shit that exists 😅
@@ -238,26 +245,79 @@ export class IntegTestRunner extends IntegRunner { | |||
lookups: this.expectedTestSuite?.enableLookups, | |||
}); | |||
} | |||
// now deploy the "actual" test. If there are any assertions | |||
// deploy the assertion stack as well | |||
this.cdk.deploy({ |
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.
Is this not async
?
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 is not, it uses spawnSync
.
* Process the outputsFile which contains the assertions results as stack | ||
* outputs | ||
*/ | ||
private processAssertionResults(file: string, assertionStackId: string): AssertionResults | undefined { |
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.
Is there a need for this to return undefined
? Can we not just return an empty object?
Motivation: I like avoiding if
s and special cases as much as I can.
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 need to check if the results have any assertion failures so I can fail the test and report on the failures. I would either need to check for undefined
or an empty object and undefined
seemed better. I can be persuaded otherwise if you think it is not though.
"Export": { | ||
"Name": "aws-cdk-lambda-destinations:ExportsOutputRefSnsSqsC4810B27404A5AFF" | ||
} |
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 is going to prevent replacements. Feels a little scary, is that definitely what we want?
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 so? I think in a lot of cases we would want to prevent replacements as that could be a breaking change. If it was really necessary then you could run without the update workflow. I'm not sure what the alternative would be that wouldn't have it's own tradeoffs.
let result: AssertionResult; | ||
let matcher; | ||
console.log(`Testing equality between ${JSON.stringify(request.actual)} and ${JSON.stringify(request.expected)}`); | ||
|
||
switch (request.assertionType) { |
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.
Instead of having a single assertionType
, could we do something arbitrarily recursive? We could encode it the same way as CloudFormation intrinsics: Something like:
{ '$ObjectLike': {
Message: [
Body: {
'Match::Like': {
Elements: { '$ArrayWith': [{ Asdf: 3 }] },
}
}
]
}}
Then in the handler we'd recursively turn that structure into matchers and arguments to matchers before executing.
On the construction side, we'd have helpers to construct this JSON so consumers don't have to see the ugliness:
export class Match {
public static arrayWith(xs: string[]): any {
return { '$ArrayWith': xs };
}
}
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.
hmm that is an interesting idea
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.
Added. Let me know what you think about the API.
status: 'fail', | ||
message: [ | ||
...matchResult.toHumanStrings(), | ||
JSON.stringify(matchResult.target, undefined, 2), | ||
].join('\n'), |
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 we choose to make the matcher succeed even if the assertion fails, do we also have a resource to make the deployment fail based on the outputs of all registered assertions? That will be necessary if we want to run assertions without the integ runner (like in CDK pipelines).
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 could just have a prop that the user could pass that would cause the custom resource to fail if the assertion fails.
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.
Added a prop, let me know what you think.
case '$ArrayWith': | ||
return Match.arrayWith(v[nested]); | ||
case '$ObjectLike': | ||
return Match.objectLike(v[nested]); | ||
case '$StringLike': | ||
return Match.stringLikeRegexp(v[nested]); |
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.
Why is this case
statement duplicated in the constructor? We shouldn't need both.
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.
Yeah that is true. Updated to not duplicate this in the constructor.
*/ | ||
public getMatcher(): Matcher { | ||
try { | ||
const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { |
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.
That's an interesting way of doing it. I would have expected an explicit recursion but I suppose this will work
scope = new Stack(scope, 'DeployAssert'); | ||
super(scope, 'Default'); |
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 is weird to me. Why is the Stack not inside the DeployAssert
?
We can hold on to the stack in a private readonly scope
so that we can still create other resources inside the right Stack. But the swapparoo here is a little confusing.
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 so that we don't have to create DeployAssert
in the scope of a stack. I was originally going to make DeployAssert
extend Stack
but I thought it would be nice if DeployAssert
could be treated like a stack, but the user wouldn't be able to access any of the stack functionality (the methods they would see would just be the DeployAssert methods and not any
Stack` methods).
So you could create any of the assertion resources by passing in DeployAssert
as the scope
const deployAssert = new DeployAssert(app);
new AwsApiCall(deployAssert, 'AwsApiCall', {...});
We could change it to what you suggested and make it a public readonly stack
which the user would have to know to use
const deployAssert = new DeployAssert(app);
new AwsApiCall(deployAssert.stack, 'AwsApiCall', {...});
*/ | ||
readonly expected: any; | ||
readonly reportFailure?: boolean; |
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.
failDeployment
?
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.
Done!
}), | ||
}; | ||
if (request.reportFailure) { | ||
throw new Error(result.data); |
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.
Is this throw
going to be shown with the error message in the stack deployment? Do we run this code through the custom resource provider?
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.
Yes this throw
will be shown in the error message in the stack deployment. The throw
is caught in the provider and returned in the response to CloudFormation.
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.
Approved modulo a comment explaining the stack swapparoo
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
This PR contains various enhancements including - `integ-tests` - removed dependency on other CDK libraries (other than core) - API ergonomics improvements - renamed `queryAws` to `awsApiCall` - added some additional methods - Now using `Match` from @aws-cdk/assertions for the assertions provider - `DeployAssert` now creates its own stack - This stack is written to a new IntegManifest property so that it can be treated differently (i.e. don't diff this stack) - Additional assertion types (OBJECT_LIKE) - Refactored assertion results - removed separate results handler in favor of just writing results to a stack output - utility for invoking lambda functions (separate from `awsApiCall`) - `IntegTest` now creates a test case by default. - Added `IntegTestCaseStack` class - `integ-runner` - Updated to handle the results of assertions - When running with update workflow, the assertion stack is only deployed during the "update" deployment - The stack outputs containing the assertion results are are written to a file that the runner can read. I've also converted/added assertions to a couple of existing integration tests - `aws-lambda/test/integ.bundling.ts` - `aws-lambda-destinations/test/integ.destinations.ts` - `aws-stepfunctions-tasks/test/eventbridge/integ.put-events.ts` ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
This PR contains various enhancements including
integ-tests
queryAws
toawsApiCall
Match
from @aws-cdk/assertions for the assertions providerDeployAssert
now creates its own stack(i.e. don't diff this stack)
awsApiCall
)IntegTest
now creates a test case by default.IntegTestCaseStack
classinteg-runner
"update" deployment
file that the runner can read.
I've also converted/added assertions to a couple of existing integration tests
aws-lambda/test/integ.bundling.ts
aws-lambda-destinations/test/integ.destinations.ts
aws-stepfunctions-tasks/test/eventbridge/integ.put-events.ts
All Submissions:
Adding new Unconventional Dependencies:
New Features
yarn integ
to deploy the infrastructure and generate the snapshot (i.e.yarn integ
without--dry-run
)?By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license