From 2a1c170aa4b11346cfb9c06f02c89a27362ac7d6 Mon Sep 17 00:00:00 2001 From: sebastian-mrozek Date: Thu, 11 Mar 2021 13:44:59 +1300 Subject: [PATCH 1/4] Add test for shuffled array comparison --- .../test/java/io/ebean/test/JsonAssertContainsTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java b/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java index 2c25776f9..1d5864a6b 100644 --- a/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java +++ b/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java @@ -83,4 +83,13 @@ public class JsonAssertContainsTest { assertThat(contains.path("a")).isEqualTo("a"); assertThat(contains.path("b")).isEqualTo("b"); } + + @Test + public void assertContainsArrayShuffled() { + JsonNode array = readNodeFromResource("/contains/array.json"); + JsonNode arrayShuffled = readNodeFromResource("/contains/array-shuffled.json"); + + JsonAssertContains.assertContains(array, arrayShuffled); + JsonAssertContains.assertContains(arrayShuffled, array); + } } From ef3980a3ee5805dda1b7cce19590aee80d73f71c Mon Sep 17 00:00:00 2001 From: sebastian-mrozek Date: Thu, 11 Mar 2021 13:47:46 +1300 Subject: [PATCH 2/4] Add test resources --- ebean-test/src/test/resources/contains/array-shuffled.json | 1 + ebean-test/src/test/resources/contains/array.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 ebean-test/src/test/resources/contains/array-shuffled.json create mode 100644 ebean-test/src/test/resources/contains/array.json diff --git a/ebean-test/src/test/resources/contains/array-shuffled.json b/ebean-test/src/test/resources/contains/array-shuffled.json new file mode 100644 index 000000000..07546c233 --- /dev/null +++ b/ebean-test/src/test/resources/contains/array-shuffled.json @@ -0,0 +1 @@ +[2, 54, 13, 10] \ No newline at end of file diff --git a/ebean-test/src/test/resources/contains/array.json b/ebean-test/src/test/resources/contains/array.json new file mode 100644 index 000000000..547a670c1 --- /dev/null +++ b/ebean-test/src/test/resources/contains/array.json @@ -0,0 +1 @@ +[10, 2, 13, 54] \ No newline at end of file From 4731ee034e3f5ee6c56f1e0bebb81f51060adbcf Mon Sep 17 00:00:00 2001 From: sebastian-mrozek Date: Mon, 15 Mar 2021 12:51:36 +1300 Subject: [PATCH 3/4] Handle searching for matching elements in an array Add unit tests. Refactor internals to allow reusing assertion method for finding matches in an array. --- .../java/io/ebean/test/CompareResult.java | 38 +++++ .../io/ebean/test/JsonAssertContains.java | 150 +++++++++++++----- .../io/ebean/test/JsonAssertContainsTest.java | 57 +++++-- .../array-multi-match-duplicate-props.json | 18 +++ .../resources/contains/array-multi-match.json | 20 +++ .../contains/array-objects-shuffled.json | 14 ++ .../resources/contains/array-objects.json | 11 ++ .../resources/contains/array-shuffled.json | 1 - .../src/test/resources/contains/array.json | 1 - 9 files changed, 256 insertions(+), 54 deletions(-) create mode 100644 ebean-test/src/main/java/io/ebean/test/CompareResult.java create mode 100644 ebean-test/src/test/resources/contains/array-multi-match-duplicate-props.json create mode 100644 ebean-test/src/test/resources/contains/array-multi-match.json create mode 100644 ebean-test/src/test/resources/contains/array-objects-shuffled.json create mode 100644 ebean-test/src/test/resources/contains/array-objects.json delete mode 100644 ebean-test/src/test/resources/contains/array-shuffled.json delete mode 100644 ebean-test/src/test/resources/contains/array.json diff --git a/ebean-test/src/main/java/io/ebean/test/CompareResult.java b/ebean-test/src/main/java/io/ebean/test/CompareResult.java new file mode 100644 index 000000000..1f2602674 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/CompareResult.java @@ -0,0 +1,38 @@ +package io.ebean.test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class CompareResult { + private final boolean applicable; + private final List errors; + + public static final CompareResult NO_ERRORS = new CompareResult(true, Collections.emptyList()); + public static final CompareResult NOT_APPLICABLE = new CompareResult(false, Collections.emptyList()); + + public static CompareResult error(String error) { + return new CompareResult(true, Collections.singletonList(error)); + } + + public static CompareResult errors(List errors) { + return new CompareResult(true, errors); + } + + private CompareResult(boolean applicable, List errors) { + this.applicable = applicable; + this.errors = new ArrayList<>(errors); + } + + public boolean isApplicable() { + return applicable; + } + + public boolean hasErrors() { + return !errors.isEmpty(); + } + + public List getErrors() { + return errors; + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/JsonAssertContains.java b/ebean-test/src/main/java/io/ebean/test/JsonAssertContains.java index 25e1e065a..2c3082605 100644 --- a/ebean-test/src/main/java/io/ebean/test/JsonAssertContains.java +++ b/ebean-test/src/main/java/io/ebean/test/JsonAssertContains.java @@ -3,28 +3,26 @@ package io.ebean.test; import com.fasterxml.jackson.databind.JsonNode; import org.assertj.core.api.Assertions; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Map; -import java.util.Stack; +import java.util.*; +import java.util.stream.Collectors; /** * Perform traversal of JsonNodes comparing against an expected JsonNode that * typically contains a subset of the data (typically excludes any generated properties * like when modified timestamps etc). */ -class JsonAssertContains { +public class JsonAssertContains { private final Stack path = new Stack<>(); - private final LinkedList errors = new LinkedList<>(); static void assertContains(JsonNode actualJsonNode, JsonNode expectedJsonNode) { new JsonAssertContains().contains(actualJsonNode, expectedJsonNode); } private void contains(JsonNode actualJsonNode, JsonNode expectedJsonNode) { - checkRecursive(null, actualJsonNode, expectedJsonNode); - if (!errors.isEmpty()) { + CompareResult result = checkRecursive(null, actualJsonNode, expectedJsonNode); + if (result.hasErrors()) { + List errors = result.getErrors(); String errorsString = String.join("\n", errors); errorsString += "\nExpected JSON fields: " + expectedJsonNode; errorsString += "\nActual JSON: " + actualJsonNode; @@ -32,55 +30,128 @@ class JsonAssertContains { } } - private void checkRecursive(String name, JsonNode actualJsonNode, JsonNode expectedJsonNode) { + private CompareResult checkRecursive(String name, JsonNode actualJsonNode, JsonNode expectedJsonNode) { if (name != null) { path.push(name); } - if (checkNull(actualJsonNode, expectedJsonNode)) { - if (checkType(actualJsonNode, expectedJsonNode)) { - if (checkArray(actualJsonNode, expectedJsonNode)) { - if (checkObject(actualJsonNode, expectedJsonNode)) { - checkValue(actualJsonNode, expectedJsonNode); - } - } - } + + CompareResult result = checkNull(actualJsonNode, expectedJsonNode); + if (result.isApplicable()) { + return pop(name, result); } + + result = checkType(actualJsonNode, expectedJsonNode); + if (result.isApplicable()) { + return pop(name, result); + } + + result = checkArray(actualJsonNode, expectedJsonNode); + if (result.isApplicable()) { + return pop(name, result); + } + + result = checkObject(actualJsonNode, expectedJsonNode); + if (result.isApplicable()) { + return pop(name, result); + } + + result = checkValue(actualJsonNode, expectedJsonNode); + if (result.isApplicable()) { + return pop(name, result); + } + + return CompareResult.NOT_APPLICABLE; + } + + private CompareResult pop(String name, CompareResult result) { if (name != null) { path.pop(); } + return result; } - private boolean checkNull(JsonNode actualJsonNode, JsonNode expectedJsonNode) { + private CompareResult checkNull(JsonNode actualJsonNode, JsonNode expectedJsonNode) { if (actualJsonNode == null) { - errors.add(String.format("Expected field '%s' to be '%s' but was null", path(), expectedJsonNode)); - return false; + return CompareResult.error(String.format("Expected field '%s' to be '%s' but was null", path(), expectedJsonNode)); } - return true; + return CompareResult.NOT_APPLICABLE; } - private boolean checkType(JsonNode actualJsonNode, JsonNode expectedJsonNode) { + private CompareResult checkType(JsonNode actualJsonNode, JsonNode expectedJsonNode) { if (!expectedJsonNode.getNodeType().equals(actualJsonNode.getNodeType())) { - errors.add(String.format("Expected field '%s' to be of type '%s' but was '%s'", path(), expectedJsonNode.getNodeType(), actualJsonNode.getNodeType())); - return false; + return CompareResult.error(String.format("Expected field '%s' to be of type '%s' but was '%s'", path(), expectedJsonNode.getNodeType(), actualJsonNode.getNodeType())); } - return true; + return CompareResult.NOT_APPLICABLE; } - private boolean checkArray(JsonNode actualJsonNode, JsonNode expectedJsonNode) { + private CompareResult checkArray(JsonNode actualJsonNode, JsonNode expectedJsonNode) { if (!expectedJsonNode.isArray()) { - return true; + return CompareResult.NOT_APPLICABLE; } - for (int i = 0; i < expectedJsonNode.size(); i++) { - checkRecursive("[" + i + "]", actualJsonNode.get(i), expectedJsonNode.get(i)); + + Map> matchingIndexes = findMatchingIndexes(actualJsonNode, expectedJsonNode); + List unmatchedIndexes = listUnmatchedIndexes(expectedJsonNode.size(), matchingIndexes); + List>> remainingEntries = removeMultipleMatches(matchingIndexes); + if (!remainingEntries.isEmpty()) { + unmatchedIndexes.addAll(remainingEntries.stream().map(Map.Entry::getKey).collect(Collectors.toList())); } - // do not continue (object or scalar type check) - return false; + + List errors = unmatchedIndexes.stream() + .map(index -> String.format("Unable to match expected element '%s[%d]' in the actual array", path(), index)) + .collect(Collectors.toList()); + + return CompareResult.errors(errors); } - private boolean checkObject(JsonNode actualJsonNode, JsonNode expectedJsonNode) { - if (!expectedJsonNode.isObject()) { - return true; + private List listUnmatchedIndexes(int size, Map> matchingIndexes) { + List unmatched = new LinkedList<>(); + for (int i = 0; i < size; i++) { + if (!matchingIndexes.containsKey(i)) { + unmatched.add(i); + } } + return unmatched; + } + + private List>> removeMultipleMatches(Map> matchingIndexes) { + List>> entries = new ArrayList<>(matchingIndexes.entrySet()); + entries.sort(Comparator.comparingInt(entry -> entry.getValue().size())); + ListIterator>> iterator = entries.listIterator(); + + while (iterator.hasNext()) { + Map.Entry> next = iterator.next(); + if (!next.getValue().isEmpty()) { + iterator.remove(); + Integer aMatchingIndex = next.getValue().stream().findFirst().get(); + removeAllMatchingIndexesOf(aMatchingIndex, entries); + } + } + + return entries; + } + + private void removeAllMatchingIndexesOf(Integer aMatchingIndex, List>> matchingIndexes) { + matchingIndexes.forEach(entry -> entry.getValue().remove(aMatchingIndex)); + } + + private Map> findMatchingIndexes(JsonNode actualJsonNode, JsonNode expectedJsonNode) { + Map> matchingElementsIndexes = new HashMap<>(); + for (int e = 0; e < expectedJsonNode.size(); e++) { + for (int a = 0; a < actualJsonNode.size(); a++) { + CompareResult result = checkRecursive("[" + e + "]", actualJsonNode.get(a), expectedJsonNode.get(e)); + if (result.isApplicable() && !result.hasErrors()) { + matchingElementsIndexes.computeIfAbsent(e, key -> new HashSet<>()).add(a); + } + } + } + return matchingElementsIndexes; + } + + private CompareResult checkObject(JsonNode actualJsonNode, JsonNode expectedJsonNode) { + if (!expectedJsonNode.isObject()) { + return CompareResult.NOT_APPLICABLE; + } + List errors = new LinkedList<>(); Iterator> expectedFields = expectedJsonNode.fields(); while (expectedFields.hasNext()) { Map.Entry expectedField = expectedFields.next(); @@ -89,17 +160,18 @@ class JsonAssertContains { if (actualNode == null) { errors.add(String.format("Expected field '%s' to be present", path(expectedKey))); } else { - checkRecursive(expectedKey, actualNode, expectedField.getValue()); + CompareResult result = checkRecursive(expectedKey, actualNode, expectedField.getValue()); + errors.addAll(result.getErrors()); } } - // do not continue (scalar type check) - return false; + return CompareResult.errors(errors); } - private void checkValue(JsonNode actualJsonNode, JsonNode expectedJsonNode) { + private CompareResult checkValue(JsonNode actualJsonNode, JsonNode expectedJsonNode) { if (!expectedJsonNode.equals(actualJsonNode)) { - errors.add(String.format("Expected field '%s' to be equal to '%s' but was '%s'", path(), expectedJsonNode, actualJsonNode)); + return CompareResult.error(String.format("Expected field '%s' to be equal to '%s' but was '%s'", path(), expectedJsonNode, actualJsonNode)); } + return CompareResult.NO_ERRORS; } String path(String expectedKey) { diff --git a/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java b/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java index 1d5864a6b..579bc2eca 100644 --- a/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java +++ b/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java @@ -1,10 +1,12 @@ package io.ebean.test; import com.fasterxml.jackson.databind.JsonNode; +import org.assertj.core.api.Assertions; import org.junit.Test; import java.util.stream.Stream; +import static io.ebean.test.Json.readNode; import static io.ebean.test.Json.readNodeFromResource; import static org.assertj.core.api.Assertions.assertThat; @@ -31,18 +33,20 @@ public class JsonAssertContainsTest { JsonNode expected = readNodeFromResource("/contains/original-subset-modified.json"); try { JsonAssertContains.assertContains(original, expected); + Assertions.fail("Expected an exception to be thrown"); } catch (AssertionError e) { String exceptionMessage = e.getMessage(); + System.out.println(exceptionMessage); Stream.of("Expected field 'someString1' to be equal to '\"aaaa\"' but was '\"string1\"", "Expected field 'someValue1' to be equal to '99' but was '1'", - "Expected field 'someArray1[0]' to be of type 'STRING' but was 'NUMBER", - "Expected field 'someArray2[0].value1' to be of type 'ARRAY' but was 'NUMBER'", - "Expected field 'someArray2[0].value2' to be of type 'OBJECT' but was 'STRING'", - "Expected field 'someArray2[0].array1[0]' to be '\"1\"' but was null", - "Expected field 'someArray2[0].object1.val5' to be present", - "Expected field 'someArray2[0].object1.val6' to be present", - "Expected field 'someArray2[0].object2' to be of type 'NULL' but was 'OBJECT'", - "Expected field 'someArray2[0].objectNull' to be of type 'OBJECT' but was 'NULL'") + "Unable to match expected element 'someArray1[0]' in the actual array", + "Expected field 'someObject1.value1' to be of type 'ARRAY' but was 'NUMBER'", + "Expected field 'someObject1.value2' to be of type 'OBJECT' but was 'STRING'", + "Unable to match expected element 'someObject1.array1[0]' in the actual array", + "Expected field 'someObject1.object1.val5' to be present", + "Expected field 'someObject1.object1.val6' to be present", + "Expected field 'someObject1.object2' to be of type 'NULL' but was 'OBJECT'", + "Expected field 'someObject1.objectNull' to be of type 'OBJECT' but was 'NULL'") .forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError)); } } @@ -54,6 +58,7 @@ public class JsonAssertContainsTest { JsonNode expected = readNodeFromResource("/contains/check-null-expected.json"); try { JsonAssertContains.assertContains(original, expected); + Assertions.fail("Expected an exception to be thrown"); } catch (AssertionError e) { String exceptionMessage = e.getMessage(); Stream.of("Expected field 'someNull' to be of type 'NULL' but was 'STRING'", @@ -68,6 +73,7 @@ public class JsonAssertContainsTest { JsonNode expected = readNodeFromResource("/contains/check-type-expected.json"); try { JsonAssertContains.assertContains(original, expected); + Assertions.fail("Expected an exception to be thrown"); } catch (AssertionError e) { String exceptionMessage = e.getMessage(); Stream.of("Expected field 'some' to be of type 'NUMBER' but was 'STRING'") @@ -84,12 +90,37 @@ public class JsonAssertContainsTest { assertThat(contains.path("b")).isEqualTo("b"); } - @Test - public void assertContainsArrayShuffled() { - JsonNode array = readNodeFromResource("/contains/array.json"); - JsonNode arrayShuffled = readNodeFromResource("/contains/array-shuffled.json"); - JsonAssertContains.assertContains(array, arrayShuffled); + @Test + public void assertContainsNumbersArrayShuffled() { + JsonNode array = readNode("[2, 54, 13, 10]"); + JsonNode arrayShuffled = readNode("[2, 13, 10, 54]"); + JsonAssertContains.assertContains(arrayShuffled, array); } + + @Test + public void assertContainsObjectsArrayShuffled() { + JsonNode array = readNodeFromResource("/contains/array-objects.json"); + JsonNode arrayShuffled = readNodeFromResource("/contains/array-objects-shuffled.json"); + + JsonAssertContains.assertContains(arrayShuffled, array); + } + + @Test + public void assertArrayElementsNotFound() { + JsonNode original = readNodeFromResource("/contains/array-multi-match.json"); + JsonNode actual = readNodeFromResource("/contains/array-multi-match-duplicate-props.json"); + + try { + JsonAssertContains.assertContains(actual, original); + Assertions.fail("Expected an exception to be thrown"); + } catch (AssertionError e) { + System.out.println(e); + String exceptionMessage = e.getMessage(); + Stream.of("Unable to match expected element '[5]' in the actual array", + "Unable to match expected element '[4]' in the actual array") + .forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError)); + } + } } diff --git a/ebean-test/src/test/resources/contains/array-multi-match-duplicate-props.json b/ebean-test/src/test/resources/contains/array-multi-match-duplicate-props.json new file mode 100644 index 000000000..4dbcb1c10 --- /dev/null +++ b/ebean-test/src/test/resources/contains/array-multi-match-duplicate-props.json @@ -0,0 +1,18 @@ +[ + { + "b": 2, + "c": 3 + }, + { + "d": 4, + "e": 5 + }, + { + "a": 1, + "b": 2, + "c": 3 + }, + { + "b": 2 + } +] diff --git a/ebean-test/src/test/resources/contains/array-multi-match.json b/ebean-test/src/test/resources/contains/array-multi-match.json new file mode 100644 index 000000000..0d106dd88 --- /dev/null +++ b/ebean-test/src/test/resources/contains/array-multi-match.json @@ -0,0 +1,20 @@ +[ + { + "a": 1 + }, + { + "b": 2 + }, + { + "c": 3 + }, + { + "d": 4 + }, + { + "e": 5 + }, + { + "f": 6 + } +] diff --git a/ebean-test/src/test/resources/contains/array-objects-shuffled.json b/ebean-test/src/test/resources/contains/array-objects-shuffled.json new file mode 100644 index 000000000..8c28a8e7a --- /dev/null +++ b/ebean-test/src/test/resources/contains/array-objects-shuffled.json @@ -0,0 +1,14 @@ +[ + { + "id": "tyu", + "c": 3 + }, + { + "id": "zxy", + "a": 1 + }, + { + "id": "123", + "b": 2 + } +] \ No newline at end of file diff --git a/ebean-test/src/test/resources/contains/array-objects.json b/ebean-test/src/test/resources/contains/array-objects.json new file mode 100644 index 000000000..bfc5e4159 --- /dev/null +++ b/ebean-test/src/test/resources/contains/array-objects.json @@ -0,0 +1,11 @@ +[ + { + "a": 1 + }, + { + "b": 2 + }, + { + "c": 3 + } +] diff --git a/ebean-test/src/test/resources/contains/array-shuffled.json b/ebean-test/src/test/resources/contains/array-shuffled.json deleted file mode 100644 index 07546c233..000000000 --- a/ebean-test/src/test/resources/contains/array-shuffled.json +++ /dev/null @@ -1 +0,0 @@ -[2, 54, 13, 10] \ No newline at end of file diff --git a/ebean-test/src/test/resources/contains/array.json b/ebean-test/src/test/resources/contains/array.json deleted file mode 100644 index 547a670c1..000000000 --- a/ebean-test/src/test/resources/contains/array.json +++ /dev/null @@ -1 +0,0 @@ -[10, 2, 13, 54] \ No newline at end of file From 899fdca9958913f07cb5bad2ca778e3a11d21abd Mon Sep 17 00:00:00 2001 From: sebastian-mrozek Date: Mon, 15 Mar 2021 13:04:05 +1300 Subject: [PATCH 4/4] Improve assertions in case json compare does not fail as expected Remove sys out print. --- .../java/io/ebean/test/JsonAssertContainsTest.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java b/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java index 579bc2eca..59d69c00b 100644 --- a/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java +++ b/ebean-test/src/test/java/io/ebean/test/JsonAssertContainsTest.java @@ -33,7 +33,6 @@ public class JsonAssertContainsTest { JsonNode expected = readNodeFromResource("/contains/original-subset-modified.json"); try { JsonAssertContains.assertContains(original, expected); - Assertions.fail("Expected an exception to be thrown"); } catch (AssertionError e) { String exceptionMessage = e.getMessage(); System.out.println(exceptionMessage); @@ -48,7 +47,9 @@ public class JsonAssertContainsTest { "Expected field 'someObject1.object2' to be of type 'NULL' but was 'OBJECT'", "Expected field 'someObject1.objectNull' to be of type 'OBJECT' but was 'NULL'") .forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError)); + return; } + Assertions.fail("Expected an exception to be thrown"); } @@ -58,13 +59,14 @@ public class JsonAssertContainsTest { JsonNode expected = readNodeFromResource("/contains/check-null-expected.json"); try { JsonAssertContains.assertContains(original, expected); - Assertions.fail("Expected an exception to be thrown"); } catch (AssertionError e) { String exceptionMessage = e.getMessage(); Stream.of("Expected field 'someNull' to be of type 'NULL' but was 'STRING'", "Expected field 'extra' to be present") .forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError)); + return; } + Assertions.fail("Expected an exception to be thrown"); } @Test @@ -73,12 +75,13 @@ public class JsonAssertContainsTest { JsonNode expected = readNodeFromResource("/contains/check-type-expected.json"); try { JsonAssertContains.assertContains(original, expected); - Assertions.fail("Expected an exception to be thrown"); } catch (AssertionError e) { String exceptionMessage = e.getMessage(); Stream.of("Expected field 'some' to be of type 'NUMBER' but was 'STRING'") .forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError)); + return; } + Assertions.fail("Expected an exception to be thrown"); } @Test @@ -114,13 +117,14 @@ public class JsonAssertContainsTest { try { JsonAssertContains.assertContains(actual, original); - Assertions.fail("Expected an exception to be thrown"); } catch (AssertionError e) { - System.out.println(e); String exceptionMessage = e.getMessage(); Stream.of("Unable to match expected element '[5]' in the actual array", "Unable to match expected element '[4]' in the actual array") .forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError)); + return; } + + Assertions.fail("Expected an exception to be thrown"); } }