-
Notifications
You must be signed in to change notification settings - Fork 15
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
(WIP) Add tests for DynamicPseudoType behavior #208
Conversation
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.
Overall super excited to get this level of end-to-end testing! There is a ton of great tests and assertions in here. My comments probably fall more under pedantic than anything.
@@ -96,6 +97,7 @@ jobs: | |||
- run: go mod download | |||
- run: go test -v -cover ./internal/framework6provider/ | |||
- run: go test -v -cover ./internal/protocolv6provider/ | |||
- run: go test -v -cover ./internal/dynamic6provider/ |
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'd be nice to not expand our packages here and use protocol(v6)provider because it means updating the terraform-plugin-go CI and I'm not sure it offers much benefit over having all the terraform-plugin-go tests in one package per protocol version. Maybe we can introduce the testsdk and update the existing provider in a separate PR, then the dynamic tests are additive to the existing 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.
That's a good shout and will make even more sense once framework implements dynamic types and we add tests for that.
I think I can wait for @bendbennett's work on #210 to merge, then will add all my tests to the protocolv6provider
+ protocolprovider
and switch them all to using the testsdk
.
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.
#210 is almost complete with the exception of dealing with hashicorp/terraform-plugin-framework#914. I'll be looking into that imminently, but don't let me hold you up in terms of merging this PR.
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 don't plan on opening this PR for review until the dynamic type work in terraform-plugin-framework
is nearing completion (which I just started), so I think we still have some time, whoever gets to it first 😆
After that work is done, I want to make sure we also have a discussion with the core team to ensure we aren't exposing dynamics in any way that could be invalid/confusing.
|
||
func Test_Dynamic_ImportState(t *testing.T) { | ||
r.UnitTest(t, r.TestCase{ | ||
Steps: []r.TestStep{ |
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.
One thing to potentially consider with this testing is whether we should make it two TestStep
so it can enable ImportStateVerify: true
and verify that there are no state differences between an originally applied state and imported state (or create a separate test like that). This is typically how import testing is done for regular providers and I think its a nice benefit even in this case since it will check the imported state contents beyond core's "schema correctness" of the import response.
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 wasn't 100% sure how to write an import test, but I took a look at the AWS provider and I think I fixed it in 95d52c2
}, | ||
}, | ||
}, | ||
PlanChangeFunc: verifyDynamicTypeByPath( |
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.
Verifying the type is awesome! Love this. While we're already so close to it, should we just verify the value as well? It could catch bizarre conversion bugs in tftypes, e.g. if the element values got errantly re-arranged for example.
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.
Sure! I adjusted the verify method to just accept a tftypes.Value
and use the type and value of that to verify. Seemed more ergonomic 😆
I broke them into two separate assertions for different messaging (even though I think (tftypes.Value).Equal
also verifies the type.) Does that make sense or should we just stick to one assert?
// The type is stored as a tuple, but we can still use `ListExact` to verify state | ||
knownvalue.ListExact([]knownvalue.Check{ | ||
knownvalue.StringExact("hey"), | ||
knownvalue.ObjectExact(map[string]knownvalue.Check{ | ||
"number": knownvalue.Int64Exact(12345), | ||
}), | ||
knownvalue.ListExact([]knownvalue.Check{ | ||
knownvalue.StringExact("there"), | ||
knownvalue.StringExact("tuple"), | ||
}), | ||
}), |
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.
@bflad @bendbennett I hesitate to suggest we add a TupleExact
, but just wanted to point out that this works fine
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.
Interesting. If there are legitimate use-cases for TupleExact
then perhaps we can add this in going forwards.
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 marvellous!
Great job.
As discussed out-of-band, perhaps we can think about documenting some of the procedures for extracting and examining msgpack
data to aid provider developers and practitioners.
@@ -96,6 +97,7 @@ jobs: | |||
- run: go mod download | |||
- run: go test -v -cover ./internal/framework6provider/ | |||
- run: go test -v -cover ./internal/protocolv6provider/ | |||
- run: go test -v -cover ./internal/dynamic6provider/ |
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.
#210 is almost complete with the exception of dealing with hashicorp/terraform-plugin-framework#914. I'll be looking into that imminently, but don't let me hold you up in terms of merging this PR.
@@ -9,7 +9,7 @@ require ( | |||
github.com/hashicorp/terraform-plugin-go v0.21.0 | |||
github.com/hashicorp/terraform-plugin-mux v0.14.0 | |||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.32.0 | |||
github.com/hashicorp/terraform-plugin-testing v1.6.0 | |||
github.com/hashicorp/terraform-plugin-testing v1.6.1-0.20240130155516-51777dda582e |
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'll tag a release once we've finalised and merged hashicorp/terraform-plugin-testing#276
"corner_dynamic_thing": { | ||
SchemaResponse: &resource.SchemaResponse{ | ||
Schema: &tfprotov6.Schema{ | ||
Block: &tfprotov6.SchemaBlock{ |
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.
Because I spend far more time looking at a schema at the "Framework-level", scanning Block: ...
always makes me think, hang on a minute, given the config that's being used this isn't a block, it's an attribute. Always makes me think of Plugin Protocol v6: What is a block, even?.
"corner_dynamic_thing.foo", | ||
tfjsonpath.New("attribute_with_dpt"), | ||
knownvalue.ListExact([]knownvalue.Check{ | ||
knownvalue.StringExact("hey"), |
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.
Might not be appropriate for this PR, or this repo even, but if it's not documented somewhere, is it worth providing some information about the rules that Terraform applies when there is ambiguity in the types specified in configuration, in this instance a string and an integer, and how that is handled in the context of a attributes that use tftypes.DynamicPseudoType
? Does Terraform take the first value (i.e., "hey"
), determine that it is a string, then determine if all of the subsequent values in the list can be converted to a string?
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'm not sure it's documented in the context you're describing, but it is an interesting thought I also share.
Just observing the behavior of Terraform/cty it looks potentially arbitrary, but digging more into cty
documentation, I believe it may just be following the only safe conversion route possible: https://github.com/zclconf/go-cty/blob/main/docs/convert.md#conversion-charts
- The literal representation of this specific test config is known to Terraform as:
tuple[string, number]
- The schema we defined tells Terraform/cty that it needs to be converted to a
list
, which has a single element type that is not determined (DynamicPseudoType) - Terraform needs to determine the element type, so it needs to find a safe route to converge
[string, number]
. Which the only safe option according to that table isstring
If we switch the config around to:
resource "corner_dynamic_thing" "foo" {
attribute_with_dpt = [12345, "hey"]
}
We receive the same element type as string
from the msgpack config:
{
"attribute_with_dpt": [
[
"\"string\"",
"12345"
],
[
"\"string\"",
"hey"
]
]
}
It would be nice to get some confirmation on this behavior, so I will note this down as something to ask the core team so we can understand a little deeper 👍🏻
// This may eventually be considered an invalid schema. It currently behaves as expected but may not be exposed to provider developers | ||
// to avoid confusion. While the element type is dynamic, all elements must still have the exact same type, which Terraform will achieve | ||
// by either performing a conversion (like below, converting 12345 to "12345"), or throw an error if conversion is impossible, for example: | ||
// | ||
// Error: Incorrect attribute value type | ||
// | ||
// on terraform_plugin_test.tf line 12, in resource "corner_dynamic_thing" "foo": | ||
// attribute_with_dpt = { | ||
// "key1" = "hey" | ||
// "key2" = { | ||
// number = 12345 | ||
// } | ||
// } | ||
// | ||
// Inappropriate value for attribute "attribute_with_dpt": all map elements must have the same type. | ||
// | ||
// Related issue: https://github.com/hashicorp/terraform/issues/34574 |
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 extensive notes/documentation.
// The type is stored as a tuple, but we can still use `ListExact` to verify state | ||
knownvalue.ListExact([]knownvalue.Check{ | ||
knownvalue.StringExact("hey"), | ||
knownvalue.ObjectExact(map[string]knownvalue.Check{ | ||
"number": knownvalue.Int64Exact(12345), | ||
}), | ||
knownvalue.ListExact([]knownvalue.Check{ | ||
knownvalue.StringExact("there"), | ||
knownvalue.StringExact("tuple"), | ||
}), | ||
}), |
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.
Interesting. If there are legitimate use-cases for TupleExact
then perhaps we can add this in going forwards.
tftypes.DynamicPseudoType, | ||
tftypes.DynamicPseudoType, |
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.
Not directly related to this PR, but given that within terraform-plugin-go
, tftypes.DynamicPseudoType
is defined as a primitive, it seems a little odd that it can be used for any Terraform type.
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.
Right, I think the documentation and wording will be important for framework developers to understand. As described by cty
, it's a placeholder for a type that will eventually be determined, "pseudo-type": https://github.com/zclconf/go-cty/blob/main/docs/types.md#the-dynamic-pseudo-type.
One consequence of this being a "pseudo-type" is that there is no known, non-null value of this type
}) | ||
} | ||
|
||
func verifyDynamicAttributeByPath(path *tftypes.AttributePath, expectedVal tftypes.Value) func(context.Context, resource.PlanChangeRequest, *resource.PlanChangeResponse) { |
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 really great!
// - https://github.com/hashicorp/terraform-plugin-go/issues/267 | ||
// - https://github.com/hashicorp/terraform/issues/34574 | ||
// | ||
// In this test, the dynamic `bar` attribute will cause the entire block to be sent over as a DynamicPseudoType, instead of just the bar attribute. |
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.
Wondering if it's worth noting that the indication that the entire block is a DynamicPseudoType is provided by the "type annotation" that appears prior to the values. Perhaps this is obvious, and maybe we should document this elsewhere, but it could be contrasted with an example where the DynamicPseudoTypes are constrained to, for instance elements, such as the Test_Dynamic_Attribute_ListType
you documented:
{
"attribute_with_dpt": [
[
"\"string\"",
"hey"
],
[
"\"string\"",
"12345"
]
]
}
@austinvalle do we still need this now that #228 is merged? |
I'm comfortable saying that the important parts of this PR are covered by #228, so I'll close this. The only tests that aren't covered in #228 are:
|
I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions. |
Ref: hashicorp/terraform-plugin-framework#147
In preparation for introducing dynamic schema attributes and type handling in
terraform-plugin-framework
, this PR adds tests to verify the behavior oftftypes.DynamicPseudoType
, which the plugin framework dynamic code will be abstracting.I tried to add comments in places where the test isn't self-explanatory ™️, but please ask questions if something looks off or an explanation doesn't describe enough.
The tests currently implemented in this PR covers resource schemas and built-in functions for Protocol v6, some follow-up tests that still need to be introduced:
testsdk
for v5 supportPR Notes
terraform-plugin-testing
, but will switch to the newest release when available