Permalink
Browse files

Merge pull request #316 from jmcgarr/fix/issue-314

Improve testability of Main.java while fixing issue #314
  • Loading branch information...
jonbullock committed Jan 4, 2017
2 parents c3aaaf2 + f4e198b commit 92fb557f3d20e00420165ee143163b9cafeac754
@@ -0,0 +1,38 @@
package org.jbake.launcher;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.VFS;
import org.apache.commons.vfs2.impl.DefaultFileMonitor;
import org.jbake.app.ConfigUtil;
/**
* Delegate responsible for watching the file system for changes.
*
* @author jmcgarr@gmail.com
*/
public class BakeWatcher {
/**
* Starts watching the file system for changes to trigger a bake.
*
* @param res Commandline options
* @param config Configuration settings
*/
public void start(final LaunchOptions res, CompositeConfiguration config) {
try {
FileSystemManager fsMan = VFS.getManager();
FileObject listenPath = fsMan.resolveFile(res.getSource(), config.getString( ConfigUtil.Keys.CONTENT_FOLDER));
System.out.println("Watching for changes in [" + res.getSource() + "]");
DefaultFileMonitor monitor = new DefaultFileMonitor(new CustomFSChangeListener(res, config));
monitor.setRecursive(true);
monitor.addFile(listenPath);
monitor.start();
} catch (FileSystemException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,35 @@
package org.jbake.launcher;
import org.apache.commons.configuration.CompositeConfiguration;
import org.jbake.app.JBakeException;
import org.jbake.app.Oven;
import java.text.MessageFormat;
import java.util.List;
/**
* Delegate class responsible for launching a Bake.
*
* @author jmcgarr@gmail.com
*/
public class Baker {
public void bake(final LaunchOptions options, final CompositeConfiguration config) {
final Oven oven = new Oven(options.getSource(), options.getDestination(), config, options.isClearCache());
oven.setupPaths();
oven.bake();
final List<Throwable> errors = oven.getErrors();
if (!errors.isEmpty()) {
final StringBuilder msg = new StringBuilder();
// TODO: decide, if we want the all errors here
msg.append( MessageFormat.format("JBake failed with {0} errors:\n", errors.size()));
int errNr = 1;
for (final Throwable error : errors) {
msg.append(MessageFormat.format("{0}. {1}\n", errNr, error.getMessage()));
++errNr;
}
throw new JBakeException(msg.toString(), errors.get(0));
}
}
}
@@ -25,7 +25,7 @@
* @param path
* @param port
*/
public static void run(String path, String port) {
public void run(String path, String port) {
Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(Integer.parseInt(port));
@@ -2,21 +2,13 @@
import java.io.File;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.List;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.VFS;
import org.apache.commons.vfs2.impl.DefaultFileMonitor;
import org.jbake.app.ConfigUtil;
import org.jbake.app.ConfigUtil.Keys;
import org.jbake.app.FileUtil;
import org.jbake.app.JBakeException;
import org.jbake.app.Oven;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.slf4j.bridge.SLF4JBridgeHandler;
@@ -31,10 +23,10 @@
private final String USAGE_PREFIX = "Usage: jbake";
private final String ALT_USAGE_PREFIX = " or jbake";
/**
* Runs the app with the given arguments.
*
*
* @param args
*/
public static void main(final String[] args) {
@@ -50,37 +42,46 @@ public static void main(final String[] args) {
}
}
private void bake(final LaunchOptions options, final CompositeConfiguration config) {
final Oven oven = new Oven(options.getSource(), options.getDestination(), config, options.isClearCache());
oven.setupPaths();
oven.bake();
final List<Throwable> errors = oven.getErrors();
if (!errors.isEmpty()) {
final StringBuilder msg = new StringBuilder();
// TODO: decide, if we want the all errors here
msg.append(MessageFormat.format("JBake failed with {0} errors:\n", errors.size()));
int errNr = 1;
for (final Throwable error : errors) {
msg.append(MessageFormat.format("{0}. {1}\n", errNr, error.getMessage()));
++errNr;
}
throw new JBakeException(msg.toString(), errors.get(0));
}
private Baker baker;
private JettyServer jettyServer;
private BakeWatcher watcher;
/**
* Default constructor.
*/
public Main() {
this(new Baker(), new JettyServer(), new BakeWatcher());
}
private void run(String[] args) {
/**
* Optional constructor to externalize dependencies.
*
* @param baker
* @param jetty
* @param watcher
*/
protected Main(Baker baker, JettyServer jetty, BakeWatcher watcher) {
this.baker = baker;
this.jettyServer = jetty;
this.watcher = watcher;
}
protected void run(String[] args) {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
LaunchOptions res = parseArguments(args);
LaunchOptions res = parseArguments( args );
final CompositeConfiguration config;
try {
config = ConfigUtil.load(res.getSource());
} catch (final ConfigurationException e) {
throw new JBakeException("Configuration error: " + e.getMessage(), e);
config = ConfigUtil.load( res.getSource() );
} catch( final ConfigurationException e ) {
throw new JBakeException( "Configuration error: " + e.getMessage(), e );
}
run(res, config);
}
protected void run(LaunchOptions res, CompositeConfiguration config) {
System.out.println("JBake " + config.getString(Keys.VERSION) + " (" + config.getString(Keys.BUILD_TIMESTAMP) + ") [http://jbake.org]");
System.out.println();
@@ -91,20 +92,23 @@ private void run(String[] args) {
}
if (res.isBake()) {
bake(res, config);
baker.bake(res, config);
}
if (res.isInit()) {
initStructure(config, res.getTemplate(), res.getSourceValue());
}
if (res.isRunServer()) {
startWatch(res, config);
watcher.start(res, config);
if (res.getSource().getPath().equals(".")) {
// use the default destination folder
runServer(config.getString(Keys.DESTINATION_FOLDER), config.getString(Keys.SERVER_PORT));
runServer( config.getString( Keys.DESTINATION_FOLDER ), config.getString( Keys.SERVER_PORT ) );
} else if (res.getDestination() != null) {
// use the destination provided via the commandline
runServer( res.getDestination().getPath(), config.getString( Keys.SERVER_PORT ));
} else {
runServer(res.getSource().getPath(), config.getString(Keys.SERVER_PORT));
runServer(config.getString( Keys.DESTINATION_FOLDER ), config.getString(Keys.SERVER_PORT));
}
}
@@ -136,7 +140,7 @@ private void printUsage(Object options) {
}
private void runServer(String path, String port) {
JettyServer.run(path, port);
jettyServer.run(path, port);
}
private void initStructure(CompositeConfiguration config, String type, String source) {
@@ -156,20 +160,5 @@ private void initStructure(CompositeConfiguration config, String type, String so
throw new JBakeException(msg, e);
}
}
private void startWatch(final LaunchOptions res, CompositeConfiguration config) {
try {
FileSystemManager fsMan = VFS.getManager();
FileObject listenPath = fsMan.resolveFile(res.getSource(), config.getString(Keys.CONTENT_FOLDER));
DefaultFileMonitor monitor = new DefaultFileMonitor(new CustomFSChangeListener(res, config));
monitor.setRecursive(true);
monitor.addFile(listenPath);
monitor.start();
} catch (FileSystemException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,105 @@
package org.jbake.launcher;
import org.apache.commons.configuration.CompositeConfiguration;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import static org.mockito.Mockito.verify;
@RunWith( MockitoJUnitRunner.class )
public class MainTest {
private Main main;
@Mock private Baker mockBaker;
@Mock private JettyServer mockJetty;
@Mock private BakeWatcher mockWatcher;
@Before
public void setup() {
this.main = new Main(mockBaker, mockJetty, mockWatcher);
}
@Test
public void launchJetty() {
String[] args = {"-s"};
main.run(args);
verify(mockJetty).run("output","8820");
}
@Test
public void launchJettyWithCustomSourceDir() {
String[] args = {"src/jbake", "-s"};
main.run(args);
verify(mockJetty).run("output","8820");
}
// Documentation states these two commands will define the custom output, but the LaunchOptions file isn't setup for that.
// I have written this test to define the existing functionality of the code and not that defined in docs.
@Test
public void launchJettyWithCustomDestinationDir() {
String[] args = {"-s", "build/jbake"};
main.run(args);
verify(mockJetty).run("output","8820");
}
@Test
public void launchJettyWithCustomSrcAndDestDir() {
String[] args = {"jbake", "build/jbake", "-s"};
main.run(args);
verify(mockJetty).run("build/jbake","8820");
}
@Test
public void launchJettyWithCustomDestViaConfig() throws CmdLineException {
String[] args = {"-s"};
Map properties = new HashMap(){{
put("destination.folder", "build/jbake");
}};
main.run(stubOptions(args), stubConfig(properties));
verify(mockJetty).run("build/jbake","8820");
}
@Test
public void launchJettyWithCmdlineOverridingProperties() throws CmdLineException {
String[] args = {"src/jbake", "build/jbake", "-s"};
Map properties = new HashMap(){{
put("destination.folder", "target/jbake");
}};
main.run(stubOptions(args), stubConfig(properties));
verify(mockJetty).run("build/jbake","8820");
}
private LaunchOptions stubOptions(String[] args) throws CmdLineException {
LaunchOptions res = new LaunchOptions();
CmdLineParser parser = new CmdLineParser(res);
parser.parseArgument(args);
return res;
}
private CompositeConfiguration stubConfig(Map<String, String> properties) {
CompositeConfiguration config = new CompositeConfiguration();
config.addProperty("server.port", "8820");
Iterator it = properties.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<String,String> pair = (Map.Entry<String,String>)it.next();
config.addProperty( pair.getKey(), pair.getValue() );
}
return config;
}
}

0 comments on commit 92fb557

Please sign in to comment.