Repository: MarvinJWendt/testza
Pull request #190 (2022-11-21 21:56:20)

fix!: use stable key ordering for snapshots with maps
Snapshots containing maps are unstable because iteration order of a map's keys is not guaranteed. This leads to test instability, where instability increases with the size of the map included in the snapshot.

Stability can be introduced by using the option of the go-spew library that sorts map keys before serialization (the [`SortKeys` option][go-spew]). The `SortKeys` option only deals with keys that are native types, or types that implement `Stringer` or `error`, but this covers a significant proportion of the uses of `map`.

**Breaking change:** This change turns the `SortKeys` option **on** for all snapshots, potentially invalidating snapshots that already exist. In reality though, given the instability of maps in snapshots currently, it's unlikely any user of the library will be affected.

## Further development

There is another option: `SpewKeys`. This extends the function of `SortKeys` to other types by first using `go-spew` to serialize the type to a string.  In my opinion, this is likely to either be (a) something a library user might want to opt into, and (b) a less-likely scenario. For this reason, I've left that option as a potential future development.

## See also

- [`go-spew` configuration reference][go-spew].

[go-spew]: https://pkg.go.dev/github.com/davecgh/go-spew/spew?utm_source=godoc#hdr-Configuration_Options


Patch:
diff --git a/snapshot.go b/snapshot.go
index 8167675..816983b 100644
--- a/snapshot.go
+++ b/snapshot.go
@@ -153,6 +153,7 @@ func createSnapshotText(object any) string {
 
 	cfg.DisablePointerAddresses = true
 	cfg.DisableCapacities = true
+	cfg.SortKeys = true
 
 	return cfg.Sdump(object)
 }


Test patch:
diff --git a/snapshot_test.go b/snapshot_test.go
index ff5b625..bc0e1ff 100644
--- a/snapshot_test.go
+++ b/snapshot_test.go
@@ -139,3 +139,39 @@ func TestSnapshotCreateOrValidate_nested_test_name(t *testing.T) {
 
 	testza.AssertNoError(t, err)
 }
+
+func TestSnapshotCreateOrValidate_key_order_stability_with_map(t *testing.T) {
+	// unsorted stable test data
+	data := []string{
+		"257ad566-2f5e-431e-b50a-d30c64849fb1",
+		"67b2fc0f-ab4a-4305-b240-deff819bb080",
+		"5778dc84-8a74-486d-ac08-ec7ff2f53348",
+		"b27e2155-af0a-46a5-ac8c-73f6d497c95c",
+		"b822f4da-57bc-4057-8c7f-7f63e71752c5",
+		"8227c74f-e621-44b8-be21-5a53f3504659",
+		"9021c878-425d-4b26-91b8-fa4c670ad111",
+		"dfc0583a-f36c-4b75-9277-98796cf5421a",
+		"5137c065-a216-45b7-a0c9-58b9425c1416",
+		"0dea5e9e-361c-4c17-87cc-fee3e41dc92d",
+		"619bbea9-bd2e-4a19-b085-33a2677ced7e",
+		"45e032f2-8eaf-4d17-9a8d-55688da19104",
+		"7591cb03-b561-4ed9-8d3f-76ee0f578d34",
+		"12787615-054e-4b09-b7ea-b81a6d30a0a1",
+		"a3563ca3-daaf-4591-afc0-f393c8b66993",
+		"e83adea8-0cd0-4fb8-843a-8f438f9f6054",
+		"78f2ab90-084b-4afc-801d-5d06d00f0761",
+		"a240ecc3-f77a-4a8b-922a-b9cde7ac7416",
+		"fc54c8c7-9a30-4b9f-a9c3-bd97df9e899b",
+		"9f58c6b7-9555-458c-9eb9-8ff400d53819",
+	}
+
+	// Create a map from unsorted data, where the value is the insertion order.
+	// Ordering stability of maps is not guaranteed.
+	snapshotMap := make(map[string]int, len(data))
+	for i, d := range data {
+		snapshotMap[d] = i
+	}
+
+	err := testza.SnapshotCreateOrValidate(t, t.Name(), snapshotMap)
+	testza.AssertNoError(t, err)
+}
diff --git a/testdata/snapshots/TestSnapshotCreateOrValidate_key_order_stability_with_map.testza b/testdata/snapshots/TestSnapshotCreateOrValidate_key_order_stability_with_map.testza
new file mode 100755
index 0000000..76d8c07
--- /dev/null
+++ b/testdata/snapshots/TestSnapshotCreateOrValidate_key_order_stability_with_map.testza
@@ -0,0 +1,22 @@
+(map[string]int) (len=20) {
+ (string) (len=36) "0dea5e9e-361c-4c17-87cc-fee3e41dc92d": (int) 9,
+ (string) (len=36) "12787615-054e-4b09-b7ea-b81a6d30a0a1": (int) 13,
+ (string) (len=36) "257ad566-2f5e-431e-b50a-d30c64849fb1": (int) 0,
+ (string) (len=36) "45e032f2-8eaf-4d17-9a8d-55688da19104": (int) 11,
+ (string) (len=36) "5137c065-a216-45b7-a0c9-58b9425c1416": (int) 8,
+ (string) (len=36) "5778dc84-8a74-486d-ac08-ec7ff2f53348": (int) 2,
+ (string) (len=36) "619bbea9-bd2e-4a19-b085-33a2677ced7e": (int) 10,
+ (string) (len=36) "67b2fc0f-ab4a-4305-b240-deff819bb080": (int) 1,
+ (string) (len=36) "7591cb03-b561-4ed9-8d3f-76ee0f578d34": (int) 12,
+ (string) (len=36) "78f2ab90-084b-4afc-801d-5d06d00f0761": (int) 16,
+ (string) (len=36) "8227c74f-e621-44b8-be21-5a53f3504659": (int) 5,
+ (string) (len=36) "9021c878-425d-4b26-91b8-fa4c670ad111": (int) 6,
+ (string) (len=36) "9f58c6b7-9555-458c-9eb9-8ff400d53819": (int) 19,
+ (string) (len=36) "a240ecc3-f77a-4a8b-922a-b9cde7ac7416": (int) 17,
+ (string) (len=36) "a3563ca3-daaf-4591-afc0-f393c8b66993": (int) 14,
+ (string) (len=36) "b27e2155-af0a-46a5-ac8c-73f6d497c95c": (int) 3,
+ (string) (len=36) "b822f4da-57bc-4057-8c7f-7f63e71752c5": (int) 4,
+ (string) (len=36) "dfc0583a-f36c-4b75-9277-98796cf5421a": (int) 7,
+ (string) (len=36) "e83adea8-0cd0-4fb8-843a-8f438f9f6054": (int) 15,
+ (string) (len=36) "fc54c8c7-9a30-4b9f-a9c3-bd97df9e899b": (int) 18
+}