Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Java type inference choosing the wrong overload?

Tags:

java

junit

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?

like image 832
molina11 Avatar asked May 01 '26 03:05

molina11


1 Answers

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.

like image 184
wero Avatar answered May 03 '26 17:05

wero