refactor: migrate Groovy tests in :src:functions to Kotlin
diff --git a/src/core/src/main/java/org/apache/jmeter/functions/gui/FunctionHelper.java b/src/core/src/main/java/org/apache/jmeter/functions/gui/FunctionHelper.java
index 3284a18..ef659af 100644
--- a/src/core/src/main/java/org/apache/jmeter/functions/gui/FunctionHelper.java
+++ b/src/core/src/main/java/org/apache/jmeter/functions/gui/FunctionHelper.java
@@ -272,7 +272,8 @@
         return sb.toString();
     }
 
-    private static String buildFunctionCallString(String functionName, Arguments args) {
+    @VisibleForTesting
+    static String buildFunctionCallString(String functionName, Arguments args) {
         StringBuilder functionCall = new StringBuilder("${");
         functionCall.append(functionName);
         if (args.getArguments().size() > 0) {
diff --git a/src/functions/src/test/kotlin/org/apache/jmeter/functions/ChangeCaseTest.kt b/src/functions/src/test/kotlin/org/apache/jmeter/functions/ChangeCaseTest.kt
index e86e5c9..fd01972 100644
--- a/src/functions/src/test/kotlin/org/apache/jmeter/functions/ChangeCaseTest.kt
+++ b/src/functions/src/test/kotlin/org/apache/jmeter/functions/ChangeCaseTest.kt
@@ -21,52 +21,59 @@
 import org.apache.jmeter.samplers.SampleResult
 import org.apache.jmeter.threads.JMeterContextService
 import org.apache.jmeter.threads.JMeterVariables
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assumptions.assumeTrue
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import java.util.Locale
 
-import spock.lang.IgnoreIf
-import spock.lang.Specification
-import spock.lang.Unroll
+class ChangeCaseTest {
+    data class ExecuteCase(val input: String, val mode: String, val output: String)
 
-@Unroll
-class ChangeCaseSpec extends Specification {
-
-    // See https://github.com/apache/jmeter/issues/5723
-    @IgnoreIf({ 'i'.toUpperCase() != 'I' || 'I'.toLowerCase() != 'i' })
-    def "convert '#input' using mode #mode to '#output'"() {
-        given:
-            def changeCase = new ChangeCase()
-            def jMCtx = JMeterContextService.getContext()
-            def result = new SampleResult()
-            result.setResponseData("dummy data", null)
-            jMCtx.setVariables(new JMeterVariables())
-            jMCtx.setPreviousResult(result)
-        when:
-            changeCase.setParameters([new CompoundVariable(input), new CompoundVariable(mode)])
-        then:
-            output == changeCase.execute(result, null)
-        where:
-            input               | mode               | output
-            "simple"            | "lower"            | "simple"
-            "simple"            | "upper"            | "SIMPLE"
-            "simple"            | "capitalize"       | "Simple"
-            "simple"            | ""                 | "SIMPLE"
-            " with space "      | "lower"            | " with space "
-            " with space "      | "upper"            | " WITH SPACE "
-            " with space "      | "capitalize"       | " with space "
-            "#_with-signs."     | "lower"            | "#_with-signs."
-            "#_with-signs."     | "upper"            | "#_WITH-SIGNS."
-            "#_with-signs."     | "capitalize"       | "#_with-signs."
-            "m4u file"          | "lower"            | "m4u file"
-            "m4u file"          | "upper"            | "M4U FILE"
-            "m4u file"          | "capitalize"       | "M4u file"
-            "WITH Ümläuts"      | "lower"            | "with ümläuts"
-            "WITH Ümläuts"      | "upper"            | "WITH ÜMLÄUTS"
-            "WITH Ümläuts"      | "capitalize"       | "WITH Ümläuts"
-            "+ - special space" | "lower"            | "+ - special space"
-            "+ - special space" | "upper"            | "+ - SPECIAL SPACE"
-            "+ - special space" | "capitalize"       | "+ - special space"
-            " "                 | "lower"            | " "
-            " "                 | "upper"            | " "
-            " "                 | "capitalize"       | " "
+    companion object {
+        @JvmStatic
+        fun executeCases() = listOf(
+            ExecuteCase("simple", "lower", "simple"),
+            ExecuteCase("simple", "upper", "SIMPLE"),
+            ExecuteCase("simple", "capitalize", "Simple"),
+            ExecuteCase("simple", "", "SIMPLE"),
+            ExecuteCase(" with space ", "lower", " with space "),
+            ExecuteCase(" with space ", "upper", " WITH SPACE "),
+            ExecuteCase(" with space ", "capitalize", " with space "),
+            ExecuteCase("#_with-signs.", "lower", "#_with-signs."),
+            ExecuteCase("#_with-signs.", "upper", "#_WITH-SIGNS."),
+            ExecuteCase("#_with-signs.", "capitalize", "#_with-signs."),
+            ExecuteCase("m4u file", "lower", "m4u file"),
+            ExecuteCase("m4u file", "upper", "M4U FILE"),
+            ExecuteCase("m4u file", "capitalize", "M4u file"),
+            ExecuteCase("WITH Ümläuts", "lower", "with ümläuts"),
+            ExecuteCase("WITH Ümläuts", "upper", "WITH ÜMLÄUTS"),
+            ExecuteCase("WITH Ümläuts", "capitalize", "WITH Ümläuts"),
+            ExecuteCase("+ - special space", "lower", "+ - special space"),
+            ExecuteCase("+ - special space", "upper", "+ - SPECIAL SPACE"),
+            ExecuteCase("+ - special space", "capitalize", "+ - special space"),
+            ExecuteCase(" ", "lower", " "),
+            ExecuteCase(" ", "upper", " "),
+            ExecuteCase(" ", "capitalize", " "),
+        ).also {
+            assumeTrue(
+                "i".uppercase(Locale.getDefault()) == "I" && "I".lowercase(Locale.getDefault()) == "i",
+                "ChangeCase does not behave well in tr_TR locale, see https://github.com/apache/jmeter/issues/5723"
+            )
+        }
     }
 
+    @ParameterizedTest
+    @MethodSource("executeCases")
+    fun changeCase(case: ExecuteCase) {
+        val changeCase = ChangeCase()
+        val jMCtx = JMeterContextService.getContext()
+        val result = SampleResult()
+        result.setResponseData("dummy data", null)
+        jMCtx.variables = JMeterVariables()
+        jMCtx.previousResult = result
+        changeCase.setParameters(listOf(CompoundVariable(case.input), CompoundVariable(case.mode)))
+
+        assertEquals(case.output, changeCase.execute(result, null))
+    }
 }
diff --git a/src/functions/src/test/kotlin/org/apache/jmeter/functions/IterationCounterTest.kt b/src/functions/src/test/kotlin/org/apache/jmeter/functions/IterationCounterTest.kt
index 5f4924d..7b1445d 100644
--- a/src/functions/src/test/kotlin/org/apache/jmeter/functions/IterationCounterTest.kt
+++ b/src/functions/src/test/kotlin/org/apache/jmeter/functions/IterationCounterTest.kt
@@ -15,51 +15,57 @@
  * limitations under the License.
  */
 
-package org.apache.jmeter.functions;
-
-import java.util.concurrent.CountDownLatch
+package org.apache.jmeter.functions
 
 import org.apache.jmeter.engine.util.CompoundVariable
 import org.apache.jmeter.threads.JMeterContextService
 import org.apache.jmeter.threads.JMeterVariables
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import java.util.concurrent.CountDownLatch
+import kotlin.concurrent.thread
 
-import spock.lang.Specification
-import spock.lang.Unroll
+class IterationCounterTest {
 
-@Unroll
-class IterationCounterSpec extends Specification {
+    @Test
+    fun `Counter per thread counts for each thread`() {
+        val context = JMeterContextService.getContext()
+        context.variables = JMeterVariables()
+        val counter = IterationCounter()
+        counter.setParameters(listOf(CompoundVariable("true"), CompoundVariable("var")))
 
-    def "Counter per thread counts for each thread"() {
-        given:
-            def context = JMeterContextService.getContext()
-            context.setVariables(new JMeterVariables())
-            def counter = new IterationCounter()
-            counter.setParameters(Arrays.asList(new CompoundVariable("true"), new CompoundVariable("var")))
-        when:
-            Thread.start({ (1..10).each { counter.execute(null, null) } }).join()
-            (1..10).each { counter.execute(null, null) }
-        then:
-            context.getVariables().get("var") == "10"
+        thread(start = true) {
+            repeat(7) { counter.execute(null, null) }
+        }.join()
+
+        repeat(10) { counter.execute(null, null) }
+
+        Assertions.assertEquals("10", context.variables.get("var")) {
+            "Only 10 executions happended in the current thread, so expecting 10. " +
+                "Note there were 7 iterations in a different thread, however the counter should be per-thread"
+        }
     }
 
-    def "global Counter counts for all threads"() {
-        given:
-            def context = JMeterContextService.getContext()
-            context.setVariables(new JMeterVariables())
-            def counter = new IterationCounter()
-            counter.setParameters(Arrays.asList(new CompoundVariable("false"), new CompoundVariable("var")))
-            def nrOfThreads = 100
-            def latch = new CountDownLatch(nrOfThreads)
-        when:
-            (1..nrOfThreads).each {
-                Thread.start({
-                    (1..1000).each { counter.execute(null, null) }
-                    latch.countDown()
-                })
+    @Test
+    fun `global Counter counts for all threads`() {
+        val context = JMeterContextService.getContext()
+        context.variables = JMeterVariables()
+        val counter = IterationCounter()
+        counter.setParameters(listOf(CompoundVariable("false"), CompoundVariable("var")))
+        val nrOfThreads = 100
+        val latch = CountDownLatch(nrOfThreads)
+
+        repeat(nrOfThreads) {
+            thread(start = true) {
+                repeat(1000) { counter.execute(null, null) }
+                latch.countDown()
             }
-            latch.await()
-            counter.execute(null, null)
-        then:
-            context.getVariables().get("var") == "100001"
+        }
+        latch.await()
+        counter.execute(null, null)
+
+        Assertions.assertEquals("100001", context.variables.get("var")) {
+            "$nrOfThreads should have increased the var by 1000, plus one in main thread"
+        }
     }
 }
diff --git a/src/functions/src/test/kotlin/org/apache/jmeter/functions/gui/FunctionHelperTest.kt b/src/functions/src/test/kotlin/org/apache/jmeter/functions/gui/FunctionHelperTest.kt
index b5e297d..3ce551e 100644
--- a/src/functions/src/test/kotlin/org/apache/jmeter/functions/gui/FunctionHelperTest.kt
+++ b/src/functions/src/test/kotlin/org/apache/jmeter/functions/gui/FunctionHelperTest.kt
@@ -19,33 +19,41 @@
 
 import org.apache.jmeter.config.Argument
 import org.apache.jmeter.config.Arguments
+import org.apache.jmeter.test.gui.DisabledIfHeadless
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
 
-import spock.lang.IgnoreIf
-import spock.lang.Specification
-import spock.lang.Unroll
+class FunctionHelperTest {
+    data class BuildCallCase(val functionName: String, val parameters: List<String>, val combined: String)
 
-@Unroll
-class FunctionHelperSpec extends Specification {
+    companion object {
+        @JvmStatic
+        fun buildCallCases() = listOf(
+            BuildCallCase("fname", listOf(), "\${fname}"),
+            BuildCallCase("fname", listOf("a"), "\${fname(a)}"),
+            BuildCallCase("fname", listOf("a,b"), "\${fname(a\\,b)}"),
+            BuildCallCase("fname", listOf("a,b,c"), "\${fname(a\\,b\\,c)}"),
+            BuildCallCase("fname", listOf("a", "b"), "\${fname(a,b)}"),
+            BuildCallCase("fname", listOf("a,b", "c"), "\${fname(a\\,b,c)}"),
+            BuildCallCase("fname", listOf("\\\${f(a,b)}"), "\${fname(\\\${f(a\\,b)})}"),
+            BuildCallCase("fname", listOf("\${f(a,b)},c,\${g(d,e)}", "h"), "\${fname(\${f(a,b)}\\,c\\,\${g(d,e)},h)}"),
+            BuildCallCase("fname", listOf("a,\${f(b,\${g(c,d)},e)},f", "h"), "\${fname(a\\,\${f(b,\${g(c,d)},e)}\\,f,h)}"),
+        )
+    }
 
-    @IgnoreIf({ System.properties['java.awt.headless'] as boolean })
-    def "construct correct call string for parameters #parameters"() {
-        setup:
-            def functionHelper = new FunctionHelper()
-        when:
-            def args = new Arguments()
-            args.setArguments(parameters.collect { new Argument("dummy${it}", it) })
-        then:
-            functionHelper.buildFunctionCallString(functionName, args).toString() == combined
-        where:
-            functionName | parameters                         | combined
-            "fname"      | []                                 | "\${fname}"
-            "fname"      | ["a"]                              | "\${fname(a)}"
-            "fname"      | ["a,b"]                            | "\${fname(a\\,b)}"
-            "fname"      | ["a,b,c"]                          | "\${fname(a\\,b\\,c)}"
-            "fname"      | ["a", "b"]                         | "\${fname(a,b)}"
-            "fname"      | ["a,b", "c"]                       | "\${fname(a\\,b,c)}"
-            "fname"      | ["\\\${f(a,b)}"]                   | "\${fname(\\\${f(a\\,b)})}"
-            "fname"      | ["\${f(a,b)},c,\${g(d,e)}", "h"]   | "\${fname(\${f(a,b)}\\,c\\,\${g(d,e)},h)}"
-            "fname"      | ["a,\${f(b,\${g(c,d)},e)},f", "h"] | "\${fname(a\\,\${f(b,\${g(c,d)},e)}\\,f,h)}"
+    @DisabledIfHeadless
+    @ParameterizedTest
+    @MethodSource("buildCallCases")
+    fun `construct correct call string for parameters #parameters`(case: BuildCallCase) {
+        val args = Arguments()
+        args.setArguments(case.parameters.map { Argument("dummy$it", it) })
+
+        assertEquals(
+            case.combined,
+            FunctionHelper.buildFunctionCallString(case.functionName, args).toString()
+        ) {
+            "buildFunctionCallString(${case.functionName}, ${case.parameters})"
+        }
     }
 }