From 900ffbe1fcb2ecce1e29b526d928c930161aa09f Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Sat, 31 Oct 2015 12:06:09 +0900 Subject: [PATCH] Add unit tests for Config.java. --- prod/net/jaekl/cfb/Config.java | 14 +++--- prod/net/jaekl/qd/util/FileIO.java | 42 +++++++++++++++++ test/net/jaekl/cfb/ConfigTest.java | 62 +++++++++++++++++++++++--- test/net/jaekl/qd/util/FileIOMock.java | 39 ++++++++++++++++ test/net/jaekl/qd/util/FileMock.java | 52 +++++++++++++++++++++ 5 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 prod/net/jaekl/qd/util/FileIO.java create mode 100644 test/net/jaekl/qd/util/FileIOMock.java create mode 100644 test/net/jaekl/qd/util/FileMock.java diff --git a/prod/net/jaekl/cfb/Config.java b/prod/net/jaekl/cfb/Config.java index 8bb401d..8aa4234 100644 --- a/prod/net/jaekl/cfb/Config.java +++ b/prod/net/jaekl/cfb/Config.java @@ -1,14 +1,16 @@ package net.jaekl.cfb; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; +import net.jaekl.qd.util.FileIO; + import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.Options; @@ -65,15 +67,15 @@ public class Config { public void readFile(File configProperties) throws IOException { Properties props = new Properties(); - FileInputStream fis = null; + InputStream is = null; try { - fis = new FileInputStream(configProperties); - props.load(fis); + is = FileIO.getInst().openInput(configProperties); + props.load(is); } finally { - if (null != fis) { - fis.close(); + if (null != is) { + is.close(); } } diff --git a/prod/net/jaekl/qd/util/FileIO.java b/prod/net/jaekl/qd/util/FileIO.java new file mode 100644 index 0000000..450d6ed --- /dev/null +++ b/prod/net/jaekl/qd/util/FileIO.java @@ -0,0 +1,42 @@ +package net.jaekl.qd.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +// Dispatch File Input/Output operations from a singleton +// This makes it possible to intercept file reads and writes +// during unit testing, and replace them with mock operations. + +public class FileIO { + // Note: need volatile to implement double-checked locking in getInst() + static volatile FileIO m_inst = null; + + FileIO() { ; } + + public static FileIO getInst() { + FileIO result = m_inst; + if (null == result) { + synchronized(FileIO.class) { + if (null == m_inst) { + m_inst = new FileIO(); + } + result = m_inst; + } + } + return result; + } + + public InputStream openInput(File file) throws FileNotFoundException + { + return new FileInputStream(file); + } + + public OutputStream openOutput(File file) throws FileNotFoundException + { + return new FileOutputStream(file); + } +} diff --git a/test/net/jaekl/cfb/ConfigTest.java b/test/net/jaekl/cfb/ConfigTest.java index 3fafe87..e7ca44c 100644 --- a/test/net/jaekl/cfb/ConfigTest.java +++ b/test/net/jaekl/cfb/ConfigTest.java @@ -1,24 +1,72 @@ package net.jaekl.cfb; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.util.List; + +import net.jaekl.qd.util.FileIOMock; +import net.jaekl.qd.util.FileMock; + +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; public class ConfigTest { + private static final String CHRIS = "chris@localhost"; + private static final String HUDSON = "hudson@jenkins.org"; + private static final String MAIL_FROM = "findbugs@jaekl.net"; + private static final String MAIL_TO = CHRIS + "," + HUDSON; + private static final String LOCALHOST = "localhost"; private static final String SAMPLE1 = "; Path (relative or absolute) to the FINDBUGS_HOME, i.e., where FindBugs is installed\n" + "FindBugsHome=../findbugs-3.0.1/\n" + "; List (comma-separated) of email addresses to which notifications should be sent\n" - + "notify=chris@localhost\n" + + "notify=" + MAIL_TO + "\n" + "\n" + "; Mail server setup\n" - + "mail.smtp.host=localhost\n" - + "mail.from=findbugs@localhost\n"; + + "mail.smtp.host=" + LOCALHOST + "\n" + + "mail.from=" + MAIL_FROM + "\n"; + @BeforeClass + public static void beforeClass() { + FileIOMock.mock_setInstance(); + } + + @AfterClass + public static void afterClass() { + FileIOMock.mock_clearInstance(); + } + @Test - public void testReadFile() { - // TODO: wrap file access so that we can test this + public void testReadFile() throws IOException { + Config config = new Config(); + + FileMock fm = new FileMock("config.properties"); + fm.mock_setContent(SAMPLE1); + + config.readFile(fm); + + assertEquals(MAIL_FROM, config.getMailFrom()); + assertEquals(LOCALHOST, config.getMailSmtpHost()); + + List notify = config.getNotify(); + assertTrue(notify.contains(CHRIS)); + assertTrue(notify.contains(HUDSON)); + } + + @Test + public void testReadEmptyFile() throws IOException { + Config config = new Config(); + FileMock fm = new FileMock("empty.properties"); + fm.mock_setContent(""); + + config.readFile(fm); + + assertEquals("findbugs@localhost", config.getMailFrom()); + assertEquals("localhost", config.getMailSmtpHost()); + assertEquals(0, config.getNotify().size()); } - } diff --git a/test/net/jaekl/qd/util/FileIOMock.java b/test/net/jaekl/qd/util/FileIOMock.java new file mode 100644 index 0000000..adfadf6 --- /dev/null +++ b/test/net/jaekl/qd/util/FileIOMock.java @@ -0,0 +1,39 @@ +package net.jaekl.qd.util; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; + +public class FileIOMock extends FileIO { + private FileIOMock() { ; } + + public static void mock_setInstance() { + synchronized(FileIO.class) { + m_inst = new FileIOMock(); + } + } + + public static void mock_clearInstance() { + synchronized(FileIO.class) { + m_inst = null; + } + } + + @Override + public InputStream openInput(File file) { + if (file instanceof FileMock) { + FileMock fm = (FileMock)file; + return fm.mock_openInput(); + } + throw new IllegalArgumentException("Expected FileMock, not File"); + } + + @Override + public OutputStream openOutput(File file) { + if (file instanceof FileMock) { + FileMock fm = (FileMock)file; + return fm.mock_openOutput(); + } + throw new IllegalArgumentException("Expected FileMock, not File"); + } +} diff --git a/test/net/jaekl/qd/util/FileMock.java b/test/net/jaekl/qd/util/FileMock.java new file mode 100644 index 0000000..a886f4c --- /dev/null +++ b/test/net/jaekl/qd/util/FileMock.java @@ -0,0 +1,52 @@ +package net.jaekl.qd.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.nio.charset.Charset; + +public class FileMock extends File { + private static final long serialVersionUID = 1L; + public static final String UTF_8 = "UTF-8"; + + private ByteArrayInputStream m_bais; + private ByteArrayOutputStream m_baos; + private byte[] m_content; + + public FileMock(String fileName) { + super(fileName); + + m_bais = null; + m_baos = null; + m_content = new byte[0]; + } + + public ByteArrayInputStream mock_openInput() { + m_bais = new ByteArrayInputStream(mock_getContent()); + return m_bais; + } + + public ByteArrayOutputStream mock_openOutput() { + m_baos = new ByteArrayOutputStream(); + return m_baos; + } + + public void mock_setContent(byte[] content) { + m_content = content.clone(); + } + + public void mock_setContent(String content, Charset charset) { + m_content = content.getBytes(charset); + } + + public void mock_setContent(String content) { + mock_setContent(content, Charset.forName(UTF_8)); + } + + public byte[] mock_getContent() { + if (null != m_baos) { + m_content = m_baos.toByteArray(); + } + return m_content.clone(); + } +} -- 2.39.2