From 8af7477b7ef210a626794487d3f5b17d63df9978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonard=20Br=C3=BCnings?= Date: Fri, 4 Aug 2023 14:21:17 +0200 Subject: [PATCH] Add array support to collection conditions (#1734) --- docs/utilities.adoc | 2 +- .../spockframework/runtime/SpockRuntime.java | 20 ++++++++++-- .../spockframework/util/ReflectionUtil.java | 3 ++ .../condition/ConditionEvaluation.groovy | 32 +++++++++++++++++++ 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/docs/utilities.adoc b/docs/utilities.adoc index 2022631b6f..59ed2c94ea 100644 --- a/docs/utilities.adoc +++ b/docs/utilities.adoc @@ -71,7 +71,7 @@ include::{sourcedir}/utilities/CollectionConditions.groovy[tag=strict-matcher] include::{sourcedir}/utilities/CollectionConditions.groovy[tag=strict-matcher-result] ---- -NOTE: Both operands must be `Iterable` for this to work. +NOTE: Both operands must either be `Iterable` or an array for this to work. Otherwise, it will be treated like the standard groovy https://groovy-lang.org/operators.html#_find_operator[find operator] or https://groovy-lang.org/operators.html#_match_operator[match operators]. [[file-stystem-fixture]] diff --git a/spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java b/spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java index 46e013c4b1..477a104709 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java +++ b/spock-core/src/main/java/org/spockframework/runtime/SpockRuntime.java @@ -17,6 +17,7 @@ package org.spockframework.runtime; import static java.util.stream.Collectors.toList; +import static org.spockframework.util.ReflectionUtil.isArray; import org.spockframework.runtime.model.*; import org.spockframework.util.*; @@ -241,7 +242,8 @@ void verify(ErrorCollector errorCollector, @Nullable List values, @Nulla int idxActual = values.indexOf(left); int idxExpected = values.indexOf(right); - if (left instanceof Iterable && right instanceof Iterable) { + + if (isIterableOrArray(left) && isIterableOrArray(right)) { if (SpockRuntime.MATCH_COLLECTIONS_AS_SET.equals(method)) { Set actual = GroovyRuntimeUtil.coerce(left, LinkedHashSet.class); Set expected = GroovyRuntimeUtil.coerce(right, LinkedHashSet.class); @@ -253,11 +255,13 @@ void verify(ErrorCollector errorCollector, @Nullable List values, @Nulla values.set(idxExpected, expected); } else { - Matcher matcher = StreamSupport.stream(((Iterable)right).spliterator(), false) + Object localLeft = convertArrayToCollectionIfNecessary(left); + Object localRight = convertArrayToCollectionIfNecessary(right); + Matcher matcher = StreamSupport.stream(((Iterable)localRight).spliterator(), false) .map(CoreMatchers::equalTo) .collect(Collectors.collectingAndThen(toList(), IsIterableContainingInAnyOrder::containsInAnyOrder)); - if (HamcrestFacade.matches(matcher, left)) { + if (HamcrestFacade.matches(matcher, localLeft)) { return; } description = HamcrestFacade.getFailureDescription(matcher, left, message); @@ -303,4 +307,14 @@ static CollectionCondition parse(Object target, String method, Object[] args, bo return null; } } + private static boolean isIterableOrArray(Object o) { + return o instanceof Iterable || isArray(o); + } + + private static Object convertArrayToCollectionIfNecessary(Object o) { + if (isArray(o)) { + return GroovyRuntimeUtil.coerce(o, List.class); + } + return o; + } } diff --git a/spock-core/src/main/java/org/spockframework/util/ReflectionUtil.java b/spock-core/src/main/java/org/spockframework/util/ReflectionUtil.java index 81742334e4..99274c6f07 100644 --- a/spock-core/src/main/java/org/spockframework/util/ReflectionUtil.java +++ b/spock-core/src/main/java/org/spockframework/util/ReflectionUtil.java @@ -71,6 +71,9 @@ public static boolean isAnnotationPresent(AnnotatedElement element, String class return false; } + public static boolean isArray(Object obj) { + return (obj != null && obj.getClass().isArray()); + } public static boolean isAnnotationPresentRecursive(Class cls, Class annotationClass) { return cls.isAnnotationPresent(annotationClass) || diff --git a/spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy b/spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy index aa10f1822f..4981969567 100644 --- a/spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy @@ -359,6 +359,38 @@ class ConditionEvaluation extends EmbeddedSpecification { } + def "collection conditions work with various types"() { + when: + runner.runFeatureBody(""" + given: + def a = [1,1,2,2,3,3] as ${aType} + def b = [1,2,3] as ${bType} + expect: + a =~ b + """) + then: + noExceptionThrown() + + where: + [aType, bType] << ['int[]', 'Integer[]', 'List', 'Set', 'Queue', 'Deque'].with { [it, it] }.combinations() + } + + def "strict collection conditions work with various types"() { + when: + runner.runFeatureBody(""" + given: + def a = [1,1,2,2,3,3] as ${aType} + def b = [1,2,3,3,2,1] as ${bType} + expect: + a ==~ b + """) + then: + noExceptionThrown() + + where: + [aType, bType] << ['int[]', 'Integer[]', 'List', 'Queue', 'Deque'].with { [it, it] }.combinations() + } + /* def "MapEntryExpression"() { // tested as part of testMapExpression