1

(Note: I have looked at similar questions (such as this where the problem is failure to trim output from shell commands) but I think this case is distinct.)

I have a pipeline script in Groovy that uses parameters (via properties([parameters([...). When I interpolate the value of the parameter into a double quoted string, it fails a .equals() check against both captured (by which I mean "captured and .trim()d!") stdout (which is my use case) and even a simple string literal.

I can work around the problem using .trim(), (even though you can see, both through my echoing them and checking the .length(), that there is nothing to .trim()), but I suspect that only "works" because it does an implicit .toString()--which is also a successful workaround.

This looks like a bug to me, but it's literally my first week working with Groovy, so perhaps I'm missing something--can anyone explain what?

Even a simple literal "foo" fails (i.e. "foo".equals("${params.foo_the_parameter}"). Is the interpolated parameter some other kind of object or something?

[EDIT After getting the answer from @Matias Bjarland, I modified the code below to use println instead of shelled echo because it makes the output more concise. The solution he suggested is reflected in a commented block.]

My groovy code:

node() {
    properties([
        parameters([
            string(
                defaultValue: 'foo',
                description: 'This is foo',
                name: 'foo_the_parameter'
            )
        ])
    ])

    /* this is what I learned from the accepted answer
    bob="${params.foo_the_parameter}"
    println("class of interpolated param is ${bob.class}")
    simple_foo="foo"
    println("class of \"foo\" is ${simple_foo.class}")
    */
    echoed_foo = sh(script:"echo 'foo'", returnStdout: true).trim()
    println "echoed foo is [$echoed_foo], params foo is [${params.foo_the_parameter}]";
    echo_foo_length = echoed_foo.length()
    dqs_foo_length = "${params.foo_the_parameter}".length()
    println "their lengths are: echo: [$echo_foo_length] and dqs: [$dqs_foo_length]";
    if (echoed_foo.equals("${params.foo_the_parameter}")) {
        println "SUCCESS they are equals()"
    }
    else {
        println "FAIL they are not equals()" //this one fires
    }
    if (echoed_foo.equals("${params.foo_the_parameter}".trim())) {
        println "SUCCESS they are equals() after the dqs gets a trim()" //this one fires
    }
    else {
        println "FAIL they are not equals()after the dqs gets a trim()"
    }
    if (echoed_foo.equals("${params.foo_the_parameter}".toString())) {
        println "SUCCESS they are equals() after the dqs gets a toString()" //this one fires
    }
    else {
        println "FAIL they are not equals()after the dqs gets a toString()"
    }

    if ("foo".equals("${params.foo_the_parameter}")) {
        println "SUCCESS at least a simple literal \"foo\" works"
    }
    else {
        println "FAIL even a simple literal \"foo\" fails to be .equals() with the interpolated parameter" //this one fires
    }
}

Jenkins output:

Started by user Michael South
[Office365connector] No webhooks to notify
Obtained jenkins.groovy from git git@github.redacted.com:msouth/test_groovy_equals.git
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] node
Running on subnet_mon_02 in /opt/jenkins/m1/workspace/field-analytics-org/test_string_equals
[Pipeline] {
[Pipeline] properties
[Pipeline] sh
[test_string_equals] Running shell script
+ echo foo
[Pipeline] echo
echoed foo is [foo], params foo is [foo]
[Pipeline] echo
their lengths are: echo: [3] and dqs: [3]
[Pipeline] echo
FAIL they are not equals()
[Pipeline] echo
SUCCESS they are equals() after the dqs gets a trim()
[Pipeline] echo
SUCCESS they are equals() after the dqs gets a toString()
[Pipeline] echo
FAIL even a simple literal "foo" fails to be .equals() with the interpolated parameter
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
[Office365connector] No webhooks to notify
Finished: SUCCESS
msouth
  • 832
  • 11
  • 21
  • In looking for "why does .equals() do this horrible thing" ;) I found this answer as well: https://stackoverflow.com/a/9682610/411177 – msouth Apr 24 '19 at 20:31
  • 1
    Don't use Java's `equals`... Try `if (echoed_foo == "${params.foo_the_parameter}") {` – tim_yates Apr 24 '19 at 20:33
  • @tim_yates I just learned about that in further searching and was coming back here to put it as a second answer. If you turn this into an answer, I will upvote it. Here is a pretty good reference: https://objectpartners.com/2018/11/07/equals-is-compareto-and-the-groovy-identity-operator/. – msouth Apr 24 '19 at 21:09

2 Answers2

4

In Groovy, you can simply use == to compare strings

In Java, you use String.equals() because str1 == str2 doesn't do what you'd expect: Java compares the references instead of the values.

In Groovy, you can just write str1 == str2 and it does what you'd expect it to do. Groovy compares the values using String.compareTo() and returns true when the result is 0.

GString g = "${'foo'}"
String s = "foo"

assert g == "foo" && s == "foo"
assert g instanceof GString && s instanceof String
assert !s.equals(g) && !g.equals(s)
assert g.compareTo(s) == 0 && s.compareTo(g) == 0
assert g == s && s == g 
Community
  • 1
  • 1
Jeremy Hunt
  • 701
  • 3
  • 10
3

Not sure if this is what you are hitting, but consider the following groovy code:

def x = 'World'
def gstr = "Hello ${x}!"
def str  = 'Hello World!'

println "class of gstr: ${gstr.class}"
println "class of str:  ${str.class}"

println(gstr.equals(str))
println(gstr.toString().equals(str))

which, when run prints:

~> groovy solution.groovy
class of gstr: class org.codehaus.groovy.runtime.GStringImpl
class of str:  class java.lang.String
false
true

~> 

in other words, a string interpolation will result in an instance of groovy GString which is not necessarily equal to the string with the same content. Forcing evaluation using .toString() solves this particular problem.

Quoting the groovy documentation on string interpolation:

Any Groovy expression can be interpolated in all string literals, apart from single and triple single quoted strings. Interpolation is the act of replacing a placeholder in the string with its value upon evaluation of the string. The placeholder expressions are surrounded by ${} or prefixed with $ for dotted expressions. The expression value inside the placeholder is evaluated to its string representation when the GString is passed to a method taking a String as argument by calling toString() on that expression.

In other words, you have to either assign the GString instance to a plan java String using some variation of:

String str1 = gstr
def str2 = gstr as String
def str3 = (String) gstr

, call a method which takes a String (which will coerce the GString to a string) or call gstr.toString() to force the conversion.

Hope that helps.

Matias Bjarland
  • 4,124
  • 1
  • 13
  • 21
  • This is exactly what I was looking for--the answer to why the .toString() made a difference when it really, really, really seems like it shouldn't. Being a complete novice to groovy, you also taught me .class and that I can use println instead of a shelled out echo. Fantastic answer! Thank you! – msouth Apr 24 '19 at 19:59