Sunday, 8 September 2013

Retrieving path to handle files making troubles during testing

Retrieving path to handle files making troubles during testing

I am working on my Ant builder for my program, specifically onto handling
path to files.
I reached a solution very simple adding in my Constant interface just two
fields:
/** The actual jar file where the class is run */
public static final File JAR_FILE = new
File(MagicHogwarts.class.getProtectionDomain().getCodeSource().getLocation().getPath());
/** The path to the main folder where the file .jar is run */
public static final String BASE_DIRECTORY =
JAR_FILE.getAbsolutePath().replace(JAR_FILE.getName(), "");
In each method that handle a file I simply ask for the path relative the
main folder where the jar is and then add the BASE_DIRECTORY first, this
is working fine at least in macOSX both in eclipse that running the jar.
Happy with this quick solution I run my tests. That failed. The problem
seems to be in the Constants interface, it's impossible to create
JAR_FILE, because getLocation returns null, and then getPath fails.
But I can't understand why.
So I tried to mock the interface, with obvious problems due to the fact
that the var is declared static final, so I tried with PowerMock:
@RunWith(PowerMockRunner.class)
@PrepareForTest({GameWindow.class,Constants.class})
public class MapReaderTest {
@Test
public void testReadingExampleMap() throws Exception {
mockStatic(GameWindow.class);
mockStatic(Constants.class);
TextureLoader tl = createMock(TextureLoader.class);
Texture tx = createMock(Texture.class);
expect(GameWindow.getTextureLoader()).andReturn(tl);
expect(Constants.BASE_DIRECTORY).andReturn("/Users/Gianmarco/Documents/java/MagicHogwarts/").anyTimes();
expect(tl.getTexture("/Users/Gianmarco/Documents/java/MagicHogwarts/bin/mh/test/sewer_tileset.png")).andReturn(tx);
expect(tx.getImageHeight()).andReturn(32).anyTimes();
expect(tx.getImageWidth()).andReturn(32).anyTimes();
replay(GameWindow.class);
replay(Constants.class);
replay(tl);
replay(tx);
// Act
Map map = new
TMXMapReader().readMap("/bin/mh/test/sewers.tmx");//mapFile.getAbsolutePath());
// Assert
assertEquals(Map.ORIENTATION_ORTHOGONAL, map.getOrientation());
assertEquals(50, map.getHeight());
assertEquals(50, map.getHeight());
assertEquals(24, map.getTileWidth());
assertEquals(24, map.getTileHeight());
assertEquals(2, map.getLayerCount());
}
}
But I got an ExceptionInInitializerError, line 60 of Constants, that is
the line where JAR_FILE is declared, the error was caused by a
NullPointerException.
The next step I thought was to transform the interface to a class, maybe
powermock was not able to handle interfaces in that way.
Run, and got a NoClassDefFoundError. Still no results, I cannot figure out
what to do. So I tried reflection.
I used this static method:
static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
// remove final modifier from field
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
calling it with:
setFinalStatic(Constants.class.getField("JAR_FILE"), null);
setFinalStatic(Constants.class.getField("BASE_DIRECTORY"),
"my/path/hardcoded");
But neither this worked, I got an ExceptionInInitializerError with a stack
that contains sun.mic.Unsafe.ensureClassInitialized(Native Method),
sun.reflect and others.
I spent hours with this problem and I came to the conclusion that the
problem is with JAR_FILE, and the method that are used to initialize it,
because even removing final modifier from the constants I was not able to
change the value calling Constants.JAR_FILE = null
The thing that I did as solution of the problem is this:
/** The actual jar file where the class is run */
public static final File JAR_FILE =
(MagicHogwarts.class.getProtectionDomain().getCodeSource().getLocation()
== null ? null : new
File(MagicHogwarts.class.getProtectionDomain().getCodeSource().getLocation().getPath()));
/** The path to the main folder where the file .jar is run */
public static final String BASE_DIRECTORY = (JAR_FILE != null ?
JAR_FILE.getAbsolutePath().replace(JAR_FILE.getName(), "") :
"/Users/Gianmarco/Documents/java/MagicHogwarts/");
that is a rude and very unpleasant and horrid way of using an if statement.
My question is, is there any different method to get the base-dir of the
jar and/or the file that is called with #java Myclass from terminal line
that can be used both in normal processing (also jar) and testing?
The solution I reached is working but I don't think is a good thing to
hardcode some testing code into a class, in fact using the if is like
telling Constants class "if I am testing, give me this mock string, else
give me the real path". That if is not useful in normal program behavior,
I whould like to leave my test code separated from my classes (as it
should be).

No comments:

Post a Comment