TESTBOX-448 MockBox $args() relies on unordered struct order#194
Open
zspitzer wants to merge 4 commits intoOrtus-Solutions:developmentfrom
Open
TESTBOX-448 MockBox $args() relies on unordered struct order#194zspitzer wants to merge 4 commits intoOrtus-Solutions:developmentfrom
zspitzer wants to merge 4 commits intoOrtus-Solutions:developmentfrom
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
$args()arg matching fails for nested struct arguments becausenormalizeArguments()falls through tostruct.toString(), whose output depends on HashMap iteration order. Two structurally-equal structs built in different insertion order hash differently, so the mock silently returnsnull.normalizeValue()helper that walks struct/array values recursively, sorting struct keys viajava.util.TreeMapso iteration order can't affect the hash. Nested CFCs are serialized via metadata to match the top-level-arg behaviour and avoid Adobe'sserializeJSONcycle on component metadata.ConcurrentHashMapNullSupportwith a thinner wrapper overjava.util.concurrent.ConcurrentHashMap. Different bucket layout = different iteration order = latent fragility becomes consistently visible. Reproduces deterministically on Lucee 7.0.3.43 too (usingstructNew("ordered")to force insertion order).Details
normalizeArguments()atsystem/MockBox.cfcsorts top-level arg keys viaTreeMap, but for non-simple values it fell through toargOrderedTree[ arg ].toString(). That's Java'sHashMap.toString()for a struct — output is not stable across iteration orders. Regular structs have never guaranteed iteration order in CFML, so this was always latent; it rarely hit in practice because tests usually build setup and call-site structs the same way. LDEV-5098 just made the fragility consistently visible.The new
normalizeValue()handles:toString()(unchanged fast path, preserves the integer++/--workaround).serializeJSON( getMetadata( value ) )(mirrors the top-level-arg branch; must run before the struct branch because on Adobe CFCs are bothisStructandisObject)..toString()withserializeJSONcatch.Test plan
testMockArgsStructOrderIndependence— struct args built in different insertion order match.testMockArgsStructContainingCFC— structs containing CFC values match and don't trigger Adobe's JSON serializer cycle.testMockArgsDeepNesting— struct → array → struct canonicalises all the way down.