From e469ec150915a836e3400b6fdcd4afb34e9bd2f5 Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Wed, 2 Oct 2024 14:32:34 +0900 Subject: [PATCH 1/9] imporve diff logging Signed-off-by: bachmanity1 --- operator-framework-core/pom.xml | 1 - ...BasedGenericKubernetesResourceMatcher.java | 58 +++++++++++++++++-- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index f0b30f33a3..e1dc2f7db2 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -58,7 +58,6 @@ org.assertj assertj-core - test io.fabric8 diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index bd4a6aa1d3..880a67a14d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -13,6 +14,7 @@ import java.util.SortedMap; import java.util.TreeMap; +import org.assertj.core.util.diff.DiffUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,19 +106,63 @@ public boolean matches(R actual, R desired, Context context) { removeIrrelevantValues(desiredMap); if (LoggingUtils.isNotSensitiveResource(desired)) { - logDiff(prunedActual, desiredMap, objectMapper); + var diff = getDiff(prunedActual, desiredMap, objectMapper); + if (diff != null) { + log.debug("Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", + actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(), diff); + } } return prunedActual.equals(desiredMap); } - private void logDiff(Map prunedActualMap, Map desiredMap, - KubernetesSerialization serialization) { + private String getDiff(Map prunedActualMap, Map desiredMap, + KubernetesSerialization serialization) { if (log.isDebugEnabled()) { - var actualYaml = serialization.asYaml(prunedActualMap); - var desiredYaml = serialization.asYaml(desiredMap); - log.debug("Pruned actual yaml: \n {} \n desired yaml: \n {} ", actualYaml, desiredYaml); + var actualLines = serialization.asYaml(sortMap(prunedActualMap)).lines().toList(); + var desiredLines = serialization.asYaml(sortMap(desiredMap)).lines().toList(); + + var patch = DiffUtils.diff(actualLines, desiredLines); + List unifiedDiff = DiffUtils.generateUnifiedDiff("", "", actualLines, patch, 0); + return unifiedDiff.isEmpty() ? null : String.join("\n", unifiedDiff); + } + return null; + } + + @SuppressWarnings("unchecked") + private Map sortMap(Map map) { + List sortedKeys = new ArrayList<>(map.keySet()); + Collections.sort(sortedKeys); + + Map sortedMap = new LinkedHashMap<>(); + for (String key : sortedKeys) { + Object value = map.get(key); + if (value instanceof Map) { + Map nestedMap = (Map) value; + sortedMap.put(key, sortMap(nestedMap)); + } else if (value instanceof List) { + sortedMap.put(key, sortList((List) value)); + } else { + sortedMap.put(key, value); + } + } + return sortedMap; + } + + @SuppressWarnings("unchecked") + private List sortList(List list) { + List sortedList = new ArrayList<>(); + for (Object item : list) { + if (item instanceof Map) { + Map mapItem = (Map) item; + sortedList.add(sortMap(mapItem)); + } else if (item instanceof List) { + sortedList.add(sortList((List) item)); + } else { + sortedList.add(item); + } } + return sortedList; } /** From bd0ff669053e5cbc84a2289b8c837080f1e964b7 Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Wed, 2 Oct 2024 14:54:41 +0900 Subject: [PATCH 2/9] compute diff only when actual doesn't match desired Signed-off-by: bachmanity1 --- .../SSABasedGenericKubernetesResourceMatcher.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index 880a67a14d..cada784ce1 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -105,7 +105,9 @@ public boolean matches(R actual, R desired, Context context) { removeIrrelevantValues(desiredMap); - if (LoggingUtils.isNotSensitiveResource(desired)) { + var matches = prunedActual.equals(desiredMap); + + if (!matches && LoggingUtils.isNotSensitiveResource(desired)) { var diff = getDiff(prunedActual, desiredMap, objectMapper); if (diff != null) { log.debug("Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", @@ -113,7 +115,7 @@ public boolean matches(R actual, R desired, Context context) { } } - return prunedActual.equals(desiredMap); + return matches; } private String getDiff(Map prunedActualMap, Map desiredMap, From 4cea98f25161f6e69429a7a25b6f384fcacaf72e Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Wed, 2 Oct 2024 15:02:25 +0900 Subject: [PATCH 3/9] slight improvements Signed-off-by: bachmanity1 --- .../SSABasedGenericKubernetesResourceMatcher.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index cada784ce1..cc0e1407ac 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -109,7 +109,7 @@ public boolean matches(R actual, R desired, Context context) { if (!matches && LoggingUtils.isNotSensitiveResource(desired)) { var diff = getDiff(prunedActual, desiredMap, objectMapper); - if (diff != null) { + if (log.isDebugEnabled()) { log.debug("Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(), diff); } @@ -120,15 +120,12 @@ public boolean matches(R actual, R desired, Context context) { private String getDiff(Map prunedActualMap, Map desiredMap, KubernetesSerialization serialization) { - if (log.isDebugEnabled()) { - var actualLines = serialization.asYaml(sortMap(prunedActualMap)).lines().toList(); - var desiredLines = serialization.asYaml(sortMap(desiredMap)).lines().toList(); + var actualLines = serialization.asYaml(sortMap(prunedActualMap)).lines().toList(); + var desiredLines = serialization.asYaml(sortMap(desiredMap)).lines().toList(); - var patch = DiffUtils.diff(actualLines, desiredLines); - List unifiedDiff = DiffUtils.generateUnifiedDiff("", "", actualLines, patch, 0); - return unifiedDiff.isEmpty() ? null : String.join("\n", unifiedDiff); - } - return null; + var patch = DiffUtils.diff(actualLines, desiredLines); + List unifiedDiff = DiffUtils.generateUnifiedDiff("", "", actualLines, patch, 0); + return String.join("\n", unifiedDiff); } @SuppressWarnings("unchecked") From dcff1baaaa2d6d27dc27d39d2a476b1ca8148c2e Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Wed, 2 Oct 2024 15:24:21 +0900 Subject: [PATCH 4/9] increase context size Signed-off-by: bachmanity1 --- .../kubernetes/SSABasedGenericKubernetesResourceMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index cc0e1407ac..e8e5651408 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -124,7 +124,7 @@ private String getDiff(Map prunedActualMap, Map var desiredLines = serialization.asYaml(sortMap(desiredMap)).lines().toList(); var patch = DiffUtils.diff(actualLines, desiredLines); - List unifiedDiff = DiffUtils.generateUnifiedDiff("", "", actualLines, patch, 0); + List unifiedDiff = DiffUtils.generateUnifiedDiff("", "", actualLines, patch, 1); return String.join("\n", unifiedDiff); } From a45f3daa64180af4e9ee8bf1e3c5a73706d07af1 Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Wed, 2 Oct 2024 16:52:58 +0900 Subject: [PATCH 5/9] fix style Signed-off-by: bachmanity1 --- .../SSABasedGenericKubernetesResourceMatcher.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index e8e5651408..83323c0673 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -106,12 +106,14 @@ public boolean matches(R actual, R desired, Context context) { removeIrrelevantValues(desiredMap); var matches = prunedActual.equals(desiredMap); - + if (!matches && LoggingUtils.isNotSensitiveResource(desired)) { var diff = getDiff(prunedActual, desiredMap, objectMapper); if (log.isDebugEnabled()) { - log.debug("Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", - actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(), diff); + log.debug( + "Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", + actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(), + diff); } } @@ -119,7 +121,7 @@ public boolean matches(R actual, R desired, Context context) { } private String getDiff(Map prunedActualMap, Map desiredMap, - KubernetesSerialization serialization) { + KubernetesSerialization serialization) { var actualLines = serialization.asYaml(sortMap(prunedActualMap)).lines().toList(); var desiredLines = serialization.asYaml(sortMap(desiredMap)).lines().toList(); From c1b86b8153b91bbd5c90b0e7fabc81ec9c3f785a Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Wed, 2 Oct 2024 17:16:28 +0900 Subject: [PATCH 6/9] calculate diff only if debug is enabled Signed-off-by: bachmanity1 --- .../SSABasedGenericKubernetesResourceMatcher.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index 83323c0673..370d6e4c4a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -107,14 +107,12 @@ public boolean matches(R actual, R desired, Context context) { var matches = prunedActual.equals(desiredMap); - if (!matches && LoggingUtils.isNotSensitiveResource(desired)) { + if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) { var diff = getDiff(prunedActual, desiredMap, objectMapper); - if (log.isDebugEnabled()) { - log.debug( - "Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", - actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(), - diff); - } + log.debug( + "Diff between actual and desired state for resource: {} with name: {} in namespace: {} is: \n{}", + actual.getKind(), actual.getMetadata().getName(), actual.getMetadata().getNamespace(), + diff); } return matches; From 611ecdb4012e471265a6891cbfd52ba163f1aee3 Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Wed, 2 Oct 2024 17:43:47 +0900 Subject: [PATCH 7/9] print actual resources when trace is enabled Signed-off-by: bachmanity1 --- .../SSABasedGenericKubernetesResourceMatcher.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index 370d6e4c4a..7bf3e07999 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -120,11 +120,15 @@ public boolean matches(R actual, R desired, Context context) { private String getDiff(Map prunedActualMap, Map desiredMap, KubernetesSerialization serialization) { - var actualLines = serialization.asYaml(sortMap(prunedActualMap)).lines().toList(); - var desiredLines = serialization.asYaml(sortMap(desiredMap)).lines().toList(); + var actualYaml = serialization.asYaml(sortMap(prunedActualMap)); + var desiredYaml = serialization.asYaml(sortMap(desiredMap)); + if (log.isTraceEnabled()) { + log.trace("Pruned actual resource: \n{} \ndesired resource: \n{} ", actualYaml, desiredYaml); + } - var patch = DiffUtils.diff(actualLines, desiredLines); - List unifiedDiff = DiffUtils.generateUnifiedDiff("", "", actualLines, patch, 1); + var patch = DiffUtils.diff(actualYaml.lines().toList(), desiredYaml.lines().toList()); + List unifiedDiff = + DiffUtils.generateUnifiedDiff("", "", actualYaml.lines().toList(), patch, 1); return String.join("\n", unifiedDiff); } From 57a0ee2f0e53b2020cd918f40386c8568fdcc309 Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Wed, 2 Oct 2024 18:24:06 +0900 Subject: [PATCH 8/9] use java-diff-utils Signed-off-by: bachmanity1 --- operator-framework-core/pom.xml | 5 +++++ .../SSABasedGenericKubernetesResourceMatcher.java | 9 ++++++--- pom.xml | 6 ++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index e1dc2f7db2..0ead5bab7f 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -14,6 +14,10 @@ Core framework for implementing Kubernetes operators + + io.github.java-diff-utils + java-diff-utils + io.fabric8 kubernetes-client @@ -58,6 +62,7 @@ org.assertj assertj-core + test io.fabric8 diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index 7bf3e07999..94a991f62d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -14,7 +14,6 @@ import java.util.SortedMap; import java.util.TreeMap; -import org.assertj.core.util.diff.DiffUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,6 +26,9 @@ import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.LoggingUtils; +import com.github.difflib.DiffUtils; +import com.github.difflib.UnifiedDiffUtils; + /** * Matches the actual state on the server vs the desired state. Based on the managedFields of SSA. * @@ -123,12 +125,13 @@ private String getDiff(Map prunedActualMap, Map var actualYaml = serialization.asYaml(sortMap(prunedActualMap)); var desiredYaml = serialization.asYaml(sortMap(desiredMap)); if (log.isTraceEnabled()) { - log.trace("Pruned actual resource: \n{} \ndesired resource: \n{} ", actualYaml, desiredYaml); + log.trace("Pruned actual resource: \n {} \ndesired resource: \n {} ", actualYaml, + desiredYaml); } var patch = DiffUtils.diff(actualYaml.lines().toList(), desiredYaml.lines().toList()); List unifiedDiff = - DiffUtils.generateUnifiedDiff("", "", actualYaml.lines().toList(), patch, 1); + UnifiedDiffUtils.generateUnifiedDiff("", "", actualYaml.lines().toList(), patch, 1); return String.join("\n", unifiedDiff); } diff --git a/pom.xml b/pom.xml index ea33898000..0983f34b08 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,7 @@ 3.1.8 0.9.11 2.16.1 + 4.12 2.11 3.12.1 @@ -161,6 +162,11 @@ ${mokito.version} + + io.github.java-diff-utils + java-diff-utils + ${java.diff.version} + org.slf4j slf4j-api From 962cea76dafaea7e7986376156098645bfcca0a0 Mon Sep 17 00:00:00 2001 From: bachmanity1 Date: Thu, 3 Oct 2024 02:19:55 +0900 Subject: [PATCH 9/9] add unit tests --- ...BasedGenericKubernetesResourceMatcher.java | 14 +++--- ...dGenericKubernetesResourceMatcherTest.java | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java index 94a991f62d..c65528eb12 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java @@ -136,7 +136,7 @@ private String getDiff(Map prunedActualMap, Map } @SuppressWarnings("unchecked") - private Map sortMap(Map map) { + Map sortMap(Map map) { List sortedKeys = new ArrayList<>(map.keySet()); Collections.sort(sortedKeys); @@ -144,10 +144,9 @@ private Map sortMap(Map map) { for (String key : sortedKeys) { Object value = map.get(key); if (value instanceof Map) { - Map nestedMap = (Map) value; - sortedMap.put(key, sortMap(nestedMap)); + sortedMap.put(key, sortMap((Map) value)); } else if (value instanceof List) { - sortedMap.put(key, sortList((List) value)); + sortedMap.put(key, sortListItems((List) value)); } else { sortedMap.put(key, value); } @@ -156,14 +155,13 @@ private Map sortMap(Map map) { } @SuppressWarnings("unchecked") - private List sortList(List list) { + List sortListItems(List list) { List sortedList = new ArrayList<>(); for (Object item : list) { if (item instanceof Map) { - Map mapItem = (Map) item; - sortedList.add(sortMap(mapItem)); + sortedList.add(sortMap((Map) item)); } else if (item instanceof List) { - sortedList.add(sortList((List) item)); + sortedList.add(sortListItems((List) item)); } else { sortedList.add(item); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java index cf0f67887b..9855527863 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcherTest.java @@ -1,5 +1,8 @@ package io.javaoperatorsdk.operator.processing.dependent.kubernetes; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -121,4 +124,47 @@ private R loadResource(String fileName, Class clazz) { fileName); } + @Test + @SuppressWarnings("unchecked") + void sortListItemsTest() { + Map nestedMap1 = new HashMap<>(); + nestedMap1.put("z", 26); + nestedMap1.put("y", 25); + + Map nestedMap2 = new HashMap<>(); + nestedMap2.put("b", 26); + nestedMap2.put("c", 25); + nestedMap2.put("a", 24); + + List unsortedListItems = Arrays.asList(1, nestedMap1, nestedMap2); + List sortedListItems = matcher.sortListItems(unsortedListItems); + + assertThat(sortedListItems).element(0).isEqualTo(1); + + Map sortedNestedMap1 = (Map) sortedListItems.get(1); + assertThat(sortedNestedMap1.keySet()).containsExactly("y", "z"); + + Map sortedNestedMap2 = (Map) sortedListItems.get(2); + assertThat(sortedNestedMap2.keySet()).containsExactly("a", "b", "c"); + } + + @Test + @SuppressWarnings("unchecked") + void testSortMapWithNestedMap() { + Map nestedMap = new HashMap<>(); + nestedMap.put("z", 26); + nestedMap.put("y", 25); + + Map unsortedMap = new HashMap<>(); + unsortedMap.put("b", nestedMap); + unsortedMap.put("a", 1); + unsortedMap.put("c", 2); + + Map sortedMap = matcher.sortMap(unsortedMap); + + assertThat(sortedMap.keySet()).containsExactly("a", "b", "c"); + + Map sortedNestedMap = (Map) sortedMap.get("b"); + assertThat(sortedNestedMap.keySet()).containsExactly("y", "z"); + } }