-
Notifications
You must be signed in to change notification settings - Fork 43
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
Refactor detailed diff v2 to short circuit on nulls and unknowns #2496
base: master
Are you sure you want to change the base?
Refactor detailed diff v2 to short circuit on nulls and unknowns #2496
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2496 +/- ##
==========================================
+ Coverage 62.58% 62.60% +0.01%
==========================================
Files 380 380
Lines 51265 51294 +29
==========================================
+ Hits 32086 32112 +26
- Misses 17363 17365 +2
- Partials 1816 1817 +1 ☔ View full report in Codecov by Sentry. |
if !isPresent(val) { | ||
return false | ||
} | ||
switch propType { |
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 logic in isTypeShapeMismatched
only works when val
doesn't have any marker types, so lets assert that.
switch propType { | |
contract.Assertf(!val.IsComputed() && !val.IsSecret() && !val.IsOutput(), | |
"Unexpected marked value %#v", val) | |
switch propType { |
if !visitor(path, val) { | ||
return false | ||
} | ||
|
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.
Again, I'd like to see an assert that we are not hitting marker types.
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.
Just was commenting on another PR, but this is not a complete walker on PropertyValue.
E.G.
If we had a complete walker on property value we would not need to double-test it. I know Go is about "a bit more duplication is better than a bit more sharing", but if we can reuse that makes coverage goals easier to achieve.
return true | ||
} | ||
|
||
func willTriggerReplacement( |
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 you comment on why we need willTriggerReplacement
and willTriggerReplacementRecursive
.
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 this code decides presentation. We already decided if the plan is replace or not. This code decides which change to blame for replacing so it's easier to see visually why a replace is happening. Is that correct VM?
return isForceNew(tfs, ps) | ||
} | ||
|
||
func willTriggerReplacementRecursive( |
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 name for this function needs to describe what it does, not how its' implemented.
if tfs.Type() != shim.TypeList && tfs.Type() != shim.TypeSet && tfs.Type() != shim.TypeMap { | ||
return false | ||
} |
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 surprised that this is (1) only effected by one level up and (2) doesn't include ForceNew on objects.
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 I understand these - can you please expand on 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.
I'm surprised that we don't search all the way up the schema tree, not just one level up.
I think I was wrong about not including objects. tfs.Type()
doesn't express objects.
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 I was wondering this too. We can probably get a away with something approximate as it's presentation-layer only but worth backlogging at least.
oldMap := old.ObjectValue() | ||
newMap := new.ObjectValue() |
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.
Just so I understand, this is ok because of isTypeShapeMismatched
?
Reviewing the tests added in #2405, they look sufficient for nested forcenew in top level and singly nested cases. Can you point me to where multiple nesting levels are tested? I want to see a test that validates our behavior on a schema like this:
I couldn't find tests for more then one level of nesting. |
func willTriggerReplacement( | ||
path propertyPath, rootTFSchema shim.SchemaMap, rootPulumiSchema map[string]*info.Schema, | ||
) bool { | ||
// A change on a property might trigger a replacement if: |
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 function seems to do what the comment says. The rule sounds a little un-intuitive but looks like you've explored this thoroughly in tests so perhaps that's that?
If a property P is forcenew, but P.a.b.c.d has changed, I am guessing that the overall plan is a replace plan, is that right? If we are displaying the detailed diff as a.b.c.d has changed
we are probably within rights to blame d
for replacing?
@@ -264,13 +294,48 @@ func (differ detailedDiffer) makePlainPropDiff( | |||
return nil | |||
} | |||
|
|||
// makeShortCircuitDiff is used for properties that are nil or computed in either the old or new state. |
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 .. tricky.. If a property is Computed or unknown, we do not know if it will trigger a replacement or not, until we know it. It might trigger a replacement, or it might not. Any implementation here takes a guess and we need to probably call this out in a comment that we are guessing. I would look up current Pulumi behavior for this case and copy how we're guessing to continue in the tradition we have.
differ := detailedDiffer{ | ||
tfs: shimv2.NewSchemaMap(sdkv2Schema), | ||
} | ||
tfs := shimv2.NewSchemaMap(sdkv2Schema) |
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 changes to tests look very unconvincing 🙏
You're going for no change in behavior in this PR?
Can you point me to a test case that illustrates this "short-circuit" diff so I can build up some intuition? Perhaps this all is under tests already. Thank you!
Do not want to block on this but would like to understand better.
This PR changes the logic in the detailed diff v2 calculation to short-circuit on encountering nils and unknowns.
Previously, when we encountered a null or unknown in either news or olds we would still recurse down and compare the non-nil values versus a nil value in order to get any replacements which might be happening. We would then simplify the diff later to trim these extra diffs but would propagate the replacement to the right level.
We would now instead short-circuit on encountering a null or unknown and walk the non-null value to find any properties which can trigger a replacement. This makes it much easier to handle sets in #2451 as recursing into sets is not necessary, as they are only compared by hashes.
This change is not meant to change any behaviour and is tested in #2405