process builder to run multi-platform postgres import

We saw how to clone a postgresql database for testing cleanly in an earlier blog post. The next step is to do so from Java so it can be part of an integration test.

How to run each of 4 DDLs:
Running a DDL file is easy which is good since we have four of them.

importDdlOrSqlFile("initialJForumSchema.ddl");
importDdlOrSqlFile("javaranchCustom.ddl");
importDdlOrSqlFile("users.ddl");
importDdlOrSqlFile("databaseChangesAfterMostRecentInstall.txt");

Ok. So maybe it is easy because all the functional code is in another method.

What is Process Builder?

Process Builder was introduced in Java 5.  The Runtime.exec JavaDoc says

ProcessBuilder.start() is now the preferred way to start a process with a modified environment.

Since I need to set two system properties, this is perfect.

How to create the Process Builder

On Windows, you just pass the command to run at the DOS prompt.  The actual command was shown in the closing a postgresql database blog post.  On UNIX/Mac/etc, you need to create a new shell and pass the command that way.  Luckily, it is easy to check for a Windows operating system:


private ProcessBuilder createProcessBuilder(String command) {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
return new ProcessBuilder(command);
}
return new ProcessBuilder("sh", "-c", command);
}

How to set system properties

I need to set the user id and password as operating system properties so postgresql doesn’t prompt for them interactively.  Using process builder, I can call environment() to get a map of environmental properties for my process.  Then setting them is simply a matter of setting them in the map.


private void importDdlOrSqlFile(String fileName) throws Exception {
String command = importToPostgresCommand(fileName);
System.out.println(command);
ProcessBuilder processBuilder = createProcessBuilder(command);
Map<String, String> env = processBuilder.environment();
env.put("PGUSER",   SystemGlobals.getValue(DATABASE_CONNECTION_USERNAME));
env.put("PGPASSWORD",  SystemGlobals.getValue(DATABASE_CONNECTION_PASSWORD));
processBuilder.redirectErrorStream(true);
runProcess(fileName, processBuilder);
}

How to run the Process Builder

All ready to run it!   start() kicks it off.  I think read all the output from the commands via the process builder’s input stream and output it to the console.  (I do this since many commands are run and the developer can see what it is up to.)  Finally, I check the error code.  After all, if the database didn’t get created properly, there isn’t much point in having a whole pile of failing tests.


private void runProcess(String fileName, ProcessBuilder processBuilder) throws IOException, InterruptedException {
Process proc = processBuilder.start();
Scanner scanner = new Scanner(proc.getInputStream());
while (scanner.hasNext()) {
String line = scanner.nextLine();
System.out.println("importing data: " + line);
}
int exitVal = proc.waitFor();
System.out.println("Completed loading " + fileName + " Exit value: " + exitVal);
if (exitVal != 0) {
System.out.println("Please fix error in database script and re-run.");
System.exit(1);
}

}

Testing

This code was tested on multiple developer’s machines including Windows and Mac.

Adobe Axes Flash CS5 iPhone Exporter

No Flash for Apple iPhone As previously reported, Apple has made it quite difficult for Adobe by banning its Flash CS5 Flash-to-iPhone converter a mere 4 days before the public release of Adobe Flash CS5. Nearly two weeks later, Adobe has responded by announcing they are ending development on the Flash CS5 iPhone compiler.

Mike Chamber, the Adobe Flash Platform project manager, made the announcement in his blog, saying:

    While it appears that Apple may selectively enforce the terms, it is our belief that Apple will enforce those terms as they apply to content created with Flash CS5. Developers should be prepared for Apple to remove existing content and applications (100+ on the store today) created with Flash CS5 from the iTunes store. We will still be shipping the ability to target the iPhone and iPad in Flash CS5. However, we are not currently planning any additional investments in that feature.

Mike also comments about the merits of the endeavor:

    So, was all of the work on the iPhone packager a waste of time and resources? No, I don’t believe so. We proved that:

      1. There is no technical reason that Flash can’t run on the iPhone
      2. Developers can create well performing and compelling content for the device with Flash

While I understand he’s trying to find a silver lining in this murky cloud, I don’t think anyone ever questioned Flash’s ability to run on an iPhone. Steve Jobs seemed more concerned with Flash’s likelihood to drain battery life than with its ability to run properly.

Finally, Mike writes that if Apple won’t have them, they will be pushing forward with the release of Flash for the Google Android.

There hasn’t been a public fight between two big powerhouses like Adobe and Apple in recent memory and it betrays the two companies’ roots. Once upon a time, Adobe’s Photoshop product was the reason you bought a Mac. It was Apple’s biggest competitive advantage, at a time when it didn’t have many. As the Adobe creative suite grew, the number of designers dependent on Apple’s products grew with it. It is hard to imagine that two companies who evolved together and owe each other for their success could have such a falling out. If Adobe really wanted to hurt Apple, they could stop releasing their creative suite products for Mac. While last month this would have sounded like an outlandish prospect, this month after such a public feud between the two companies, it seems well within the realm of possibilities.

junit suite wide setup (not @BeforeClass)

Problem

How do I run setup/teardown for my whole suite, not just one class/test?

Impact of Problem

JUnit offers @Before to run a method before each test and @BeforeClass to run a method once before a class.  For a small suite, @BeforeClass may be enough.  For a large integration test suite, setting up the database is a common task that should be done once for scores of tests.  Having everything in one class is not optimal.  Nor is manually listing all the tests.

Requirements

  1. Continue being able to use ClasspathSuite to gather tests in a subdirectory at runtime.
  2. Run a method before the first of these tests is run
  3. Run a method after the last of these tests is run
  4. Take advantage of JUnit’s runner for running all the tests

Solution

package com.javaranch.test.functional;

import static org.junit.extensions.cpsuite.SuiteType.*;

import org.junit.extensions.cpsuite.*;
import org.junit.extensions.cpsuite.ClasspathSuite.*;
import org.junit.runner.*;

// use cpsuite to dynamically list out tests
@RunWith(ClasspathSuite.class)
@ClassnameFilters( { "com.javaranch.*test.*Test", "net.jforum.*test.*Test" })
// include in case have a parameterized test case
@SuiteTypes( { RUN_WITH_CLASSES, TEST_CLASSES, JUNIT38_TEST_CLASSES })
public class All_JForum_Functional_Tests {
public static void main(String[] args) throws Exception {
setUp();
JUnitCore.main("com.javaranch.test.functional.All_JForum_Functional_Tests");
tearDown();
}

private static void setUp() {
// create the database
}

private static void tearDown() {
// destroy the database
}
}

If you’ve been reading my blog, you’ll know I am doing this to integration test the back end of JavaRanch’s JForum install.  I started by cloning a database and have now moved on to the integration test framework.  Next I will describe the actual database setup.