I'm working on a kotlin application to send data to a third party application. The vendor has provided a jar file that we will need to run in order to actually transmit the data to them (for more details see: How do I execute a jar file as part of a kotlin application?)
While I have figured out how to execute the jar using java.lang.ProcessBuilder, I am struggling to implement a unit test. I wanted to create a test of my logic that calls the jar file without having to worry about the contents of the actual jar. To do that I created a simple java class, packaged it into a jar, and then put that jar into src/test/resources.
In the unit test, I then load the jar as an input stream via a class loader. That input stream then gets written to a tmp file. When I call my function using that tmp file I get an error like this:
java.lang.Exception: Error: Unable to access jarfile hello15286565382928855962.jar
After some experimenting, I found that I can get around this by passing the absolute file path and the original (non-tmp) file to my function. The problem is, that will only work on my local machine and I need this test to be compatible with a CI pipeline.
So then, how to I load a test resource and get the path and file name without needing a hard coded file path?
My dummy class (HelloTest.jar):
public class Main {
public static void main(String[] args) throws Exception {
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
// to see how IntelliJ IDEA suggests fixing it.
System.out.printf("Hello and welcome!");
if( args.length > 1 ) {
throw new Exception("Too many args");
}
for (int i = 1; i <= 5; i++) {
//TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.
System.out.println("i = " + i);
}
}
}
My kotlin function to run the jar:
internal fun executeJavaJar(workingDir:String, jarFile:String, args:List<String>): Either<Throwable, String>
{
return runCatching {
val cmdArgs: MutableList<String> = mutableListOf("java", "-jar", jarFile)
cmdArgs.addAll(args)
ProcessBuilder(cmdArgs).directory(File(workingDir))
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE).start()
}.fold({
it->
val output = IOUtils.toString( it.inputStream, Charsets.UTF_8)
val error = IOUtils.toString( it.errorStream, Charsets.UTF_8)
if(error.isNotEmpty())
{
throw Exception(error)
}
else {
output.right()
}
}, {
error->
error.left()
})
}
My unit test:
@Test
fun `test execute java jar`()
{
mockContext(300).value().let { context ->
testInt295DependencyProvider(context).let { dp ->
javaClass.classLoader.getResourceAsStream("HelloTest.jar").use { stream ->
//this works locally but would fail in a CI build
executeJavaJar("/Users/userA/Documents/workspace/app-event-management/lambda-INT295/src/test/resources",
"HelloTest.jar", listOf("abc")).fold({
fail("test execute java jar failed with error: ${it.message}")
}, {
output->
println(output)
assertNotNull(output)
assertThat(output).isNotEmpty
})
}
}
}
}
@Test
fun `test execute java jar with tmp`()
{
mockContext(300).value().let { context ->
testInt295DependencyProvider(context).let { dp ->
javaClass.classLoader.getResourceAsStream("HelloTest.jar").use { stream ->
assertNotNull(stream)
val inputFile = createTempFile(prefix = "hello", suffix = ".jar").toFile()
inputFile.copyInputStreamToFile(stream)
//this throws an access error
executeJavaJar(".",
inputFile.name, listOf("abc")).fold({
fail("test execute java jar failed with error: ${it.message}")
}, {
output->
println(output)
assertNotNull(output)
assertThat(output).isNotEmpty
})
}
}
}
}
As k314159 said in the comments, you are running java -jar
in the current directory, but you are not passing the whole path of the jar file to java -jar
. The temporary jar file is created in a temporary directory, which probably is not the current directory.
You can pass the absolutePath
of the file instead:
executeJavaJar(".", inputFile.absolutePath, listOf("abc"))