Skip to content
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

Improved consistency of map key sorting. #439

Open
wants to merge 1 commit into
base: v2
Choose a base branch
from

Conversation

joshdk
Copy link

@joshdk joshdk commented Feb 23, 2019

There are some specific sets of keys that will be sorted inconsistently when marshaling. This can result in YAML documents that change significantly when re-marshaled, even if the underlying data remains identical.

I noticed this when marshaling a cache object (vanilla map[string]string) as part of a CI pipeline. The resulting (git) diff kept thrashing even if the cache data didn't.

As a minimal repro, I was able to find a set of keys that trigger this behavior. These keys do not have a strict ordering, as z01 < z12, z12 < z1a, and z1a < z01, creating a cycle.

func (s *S) TestSortedMap(c *C) {
	var (
		m = map[string]struct{}{
			"z01": {},
			"z12": {},
			"z1a": {},
		}

		expected = []byte("z01: {}\nz12: {}\nz1a: {}\n")
	)

	// Repeatedly marshal the same exact data, and check for any differences in
	// the output.
	for i := 0; i < 100; i++ {
		actual, err := yaml.Marshal(m)
		c.Assert(err, IsNil)

		if bytes.Compare(expected, actual) != 0 {
			fmt.Printf("Expected: \n%s\n", string(expected))
			fmt.Printf("Actual:   \n%s\n", string(actual))
			c.Fatalf("Difference on iteration %d", i)
		}
	}
}
Expected: 
z01: {}
z12: {}
z1a: {}

Actual:   
z12: {}
z1a: {}
z01: {}

<snip>
Error: Difference on iteration 4

The changes in this PR removes this inconsistency, while maintaining the same ordering behavior asserted in the existing test suite.

Some specific feedback that I think this PR needs:

  • How you would like to test functionality of private (yaml) package contents? - All current tests exist in an external (yaml_test) package, and cannot assert this behavior directly.
  • How would you like to test functionality that has not made it into v2 (or v3, etc)? - All current tests import "gopkg.in/yaml.v2" which would make new tests fail as that package doesn't yet contain the corresponding new fixes.

Looking forward to hearing your feedback, cheers!

@joshdk
Copy link
Author

joshdk commented Mar 4, 2019

Do you have any feedback @niemeyer and @rogpeppe? Thank you!

dominikgrygiel added a commit to dominikgrygiel/yaml that referenced this pull request Jun 2, 2020
@dominikgrygiel
Copy link

I've tested this change and it works great 🥇

Another way of unit testing this change could be with something like:

var keys1 keyList = keyList{reflect.ValueOf("z01"), reflect.ValueOf("z13"), reflect.ValueOf("z1a")}
var keys2 keyList = keyList{reflect.ValueOf("z1a"), reflect.ValueOf("z01"), reflect.ValueOf("z13")}

sort.Sort(keys1)
sort.Sort(keys2)
// compare keys1 and keys2, would be different before the fix and the same after this fix

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.

None yet

2 participants