3
public class MyProperties {
  private Map<String, Object> properties = new HashMap<String, Object>();

  public void setProperty(String name, Object value) {
      properties.put(name, value);
  }

  @SuppressWarnings("unchecked")
  public <T> T getProperty(String name) {
      return (T) properties.get(name);
  }
}

@Test
public void test() {
    MyProperties props1 = new MyProperties();
    props1.setProperty("name", "John Smith");

    MyProperties props2 = new MyProperties();
    props2.setProperty("name", "John Smith");

    assertEquals(props2.getProperty("name"), props1.getProperty("name"));
}

The above unit tests passes on my machine but fails in our jenkins environment with the following error:

java.lang.String cannot be cast to [Ljava.lang.Object;

The unit test passes on my machine when run via Eclipse and ant (eclipse 4.4 luna, ant 1.9.6, jdk_8_u60, Windows 7 64bit) but fails in our jenkins environment (ant 1.9.6 jdk_8_u60, Ubuntu 12.04.4). It's also failing in several other environments, and working in several other -- with no apparent rhyme or reason.

So I have two questions:

1)Why is Java choosing the overload org.junit.Assert.assertEquals(Object[],Object[]) instead of org.junit.Assert.assertEquals(String,String) or org.junit.Assert.assertEquals(Object,Object)?

2) and why is the unit test passing in some environments, and failing in others with the same versions of java, ant, and junit?

molina11
  • 181
  • 4
  • 6
    The signature of your `getProperty` method, by the way, is deeply evil. There are ways to do type-safe mapping for arbitrary properties. This is not it. – Louis Wasserman Sep 08 '15 at 19:02
  • Why your `getProperty()` method is generic? I would just keep the return type as `Object`. – Rohit Jain Sep 08 '15 at 19:03
  • @LouisWasserman You're completely right. Why using T and Object at the same time? – Rafal G. Sep 08 '15 at 19:03
  • 1
    @molina11: do you get a deprecation warning for the `assertEquals` line when you compile in Eclipse? – wero Sep 08 '15 at 19:17
  • @LouisWasserman: I find your comment a little bit harsh. You should at least give a reference to a more suitable solution. – wero Sep 08 '15 at 19:21
  • @wero: no but the UT is passing in Eclipse – molina11 Sep 08 '15 at 19:29
  • Slapped together something at http://ideone.com/LuG2S4. The trick is not using `Map` directly and instead using something specialized to generic `Key` types. – Louis Wasserman Sep 08 '15 at 19:31
  • @molina11 May I ask for the complete stacktrace, please? – Little Santi Sep 08 '15 at 19:38
  • @RohitJain: The implementation of the MyProperties class is not important. This issue occurs when the results of a generic method are passed in to a method with multiple overloads. I just created a bare-minimum test case that exhibits the same problems as my main code-base, which I cannot share here – molina11 Sep 08 '15 at 19:39
  • @LittleSanti the stacktrace is: java.lang.ClassCastException: java.lang.String cannot be cast to [Ljava.lang.Object; at com.foo.bar.FooBar_UT.test(FooBar_UT.java:27) – molina11 Sep 08 '15 at 19:40

1 Answers1

3

Regarding 2:

The different behaviour is most likely not caused by the runtime environment, but by the compiler used to compile the class.

Eclipse has its own Java compiler whereas Jenkins uses javac from the JDK.

Seems that Eclipse 4.4. generates a call for the assertEquals line which allows for a successful test run whereas javac generates a call to org.junit.Assert.assertEquals(Object[],Object[]) and then the test fails.

This does not work in Eclipse 4.5 which will at compile time complain about the call to the deprecated method Assert.assertEquals(Object[],Object[]) and the test also fails when run in Eclipse 4.5.

You can test the hypothesis by decompiling the classes generated by Eclipse 4.4 and javac (using (javap -c) and examine what Assert method they chose.

Regarding 1:

Assert has two assertEquals-methods which accept two objects:

Assert.assertEquals(Object expected, Object actual);
Assert.assertEquals(Object[] expecteds, Object[] actuals);

According to the JLS (15.12) the compiler has to choose the most specific from all applicable methods when when compiling a method invocation. In the example this is Assert.assertEquals(Object[] expecteds, Object[] actuals).

This might be confusing, and because of that JUnit could have decided to deprecate the method with array arguments.

wero
  • 32,544
  • 3
  • 59
  • 84
  • what happened to `assertEquals(String,String)`. there used to be a method like that. removed? – ZhongYu Sep 08 '15 at 20:35
  • This method does not exist in the current JUnit. Don't know if it ever existed. Probably not (https://stackoverflow.com/questions/1201927/java-is-assertequalsstring-string-reliable) – wero Sep 08 '15 at 20:40
  • ok, it's in a different `Assert` :) - https://github.com/junit-team/junit/blob/r4.12/src/main/java/junit/framework/Assert.java#L103 – ZhongYu Sep 08 '15 at 20:47
  • I'm still unclear on why the compiler is choosing the `Assert.assertEquals(Object[] expecteds, Object[] actuals)` overload when I'm passing in Strings, not Arrays – molina11 Sep 08 '15 at 20:51
  • @molina11: you are not passing in Strings, but some T1 and T2. Don't think in terms of actual objects in the properties object but in terms what the compiler sees. – wero Sep 08 '15 at 20:55