From f1c4313e9229dd2d5f7fd984169cbdb89fef4cd5 Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Sun, 27 Dec 2015 22:13:17 +0900 Subject: [PATCH] Work toward improving solidity. Add a few more unit tests, and some toString() methods that make debugging variable dumps more useful. --- prod/net/jaekl/cfb/analyze/Analysis.java | 1 + prod/net/jaekl/cfb/db/Condition.java | 5 + prod/net/jaekl/cfb/db/Row.java | 18 ++ prod/net/jaekl/cfb/util/Util.java | 44 ++++- prod/net/jaekl/cfb/xml/BugCollection.java | 13 ++ test/net/jaekl/cfb/db/TableMock.java | 14 +- .../net/jaekl/cfb/db/driver/DbDriverMock.java | 107 +++++++++++- test/net/jaekl/cfb/store/DbStoreTest.java | 163 ++++++++++++++++++ test/net/jaekl/cfb/util/UtilTest.java | 69 ++++++++ 9 files changed, 425 insertions(+), 9 deletions(-) create mode 100644 test/net/jaekl/cfb/store/DbStoreTest.java create mode 100644 test/net/jaekl/cfb/util/UtilTest.java diff --git a/prod/net/jaekl/cfb/analyze/Analysis.java b/prod/net/jaekl/cfb/analyze/Analysis.java index 81f4b89..d720184 100644 --- a/prod/net/jaekl/cfb/analyze/Analysis.java +++ b/prod/net/jaekl/cfb/analyze/Analysis.java @@ -31,6 +31,7 @@ public class Analysis { m_buildNumber = buildNumber; m_start = new Date().getTime(); m_end = 0; + m_bugCollection = new BugCollection(); } public BugCollection getBugCollection() { return m_bugCollection; } diff --git a/prod/net/jaekl/cfb/db/Condition.java b/prod/net/jaekl/cfb/db/Condition.java index 98bd5f2..75bfe29 100644 --- a/prod/net/jaekl/cfb/db/Condition.java +++ b/prod/net/jaekl/cfb/db/Condition.java @@ -30,4 +30,9 @@ public class Condition { public Column getColumn() { return m_column; } public Object getValue() { return m_value; } public Operation getOperation() { return m_operation; } + + @Override + public String toString() { + return ("(" + m_column.getName() + " " + m_operation + " " + m_value + ")"); + } } diff --git a/prod/net/jaekl/cfb/db/Row.java b/prod/net/jaekl/cfb/db/Row.java index 35bf121..2200aa1 100644 --- a/prod/net/jaekl/cfb/db/Row.java +++ b/prod/net/jaekl/cfb/db/Row.java @@ -14,6 +14,10 @@ public class Row { public int getNumColumns() { return m_columns.length; } public Column getColumn(int idx) { return m_columns[idx]; } + public Object getValue(int index) { + return m_values[index]; + } + public String getString(int index) throws TypeMismatchException { checkType(index, Column.Type.VARCHAR); return (String)m_values[index]; @@ -43,6 +47,20 @@ public class Row { return (java.util.Date)m_values[index]; } + @Override + public String toString() + { + StringBuilder sb = new StringBuilder("["); + for (int idx = 0; idx < m_columns.length; ++idx) { + if (idx > 0) { + sb.append(", "); + } + sb.append("" + m_columns[idx].getName() + "=" + m_values[idx]); + } + sb.append("]"); + return sb.toString(); + } + protected void checkType(int index, Column.Type type) throws TypeMismatchException { Column column = m_columns[index]; Column.Type columnType = column.getType(); diff --git a/prod/net/jaekl/cfb/util/Util.java b/prod/net/jaekl/cfb/util/Util.java index 309e20d..d1e79bd 100644 --- a/prod/net/jaekl/cfb/util/Util.java +++ b/prod/net/jaekl/cfb/util/Util.java @@ -4,14 +4,38 @@ package net.jaekl.cfb.util; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Iterator; +import java.util.List; public class Util { - public static String stringify(Throwable thr) + // Returns true iff. a and b contain equal items in the same order + public static boolean listsAreEqual(List a, List b) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - thr.printStackTrace(pw); - return sw.toString(); + if ((null == a) || (null == b)) { + return (a == b); + } + + if (0 == a.size()) { + return (0 == b.size()); + } + + if (a.size() != b.size()) { + return false; + } + + Iterator iterA = a.iterator(); + Iterator iterB = b.iterator(); + + while (iterA.hasNext()) { + Object elemA = iterA.next(); + Object elemB = iterB.next(); + + if (! objsAreEqual(elemA, elemB)) { + return false; + } + } + + return true; } // Test for equality, while taking care to avoid @@ -34,4 +58,14 @@ public class Util { } return obj.hashCode(); } + + // Convert a Throwable to the string representation + // that is generated by printStackTrace(). + public static String stringify(Throwable thr) + { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + thr.printStackTrace(pw); + return sw.toString(); + } } diff --git a/prod/net/jaekl/cfb/xml/BugCollection.java b/prod/net/jaekl/cfb/xml/BugCollection.java index 98ea93b..0ce05ec 100644 --- a/prod/net/jaekl/cfb/xml/BugCollection.java +++ b/prod/net/jaekl/cfb/xml/BugCollection.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import net.jaekl.cfb.util.Util; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -52,4 +53,16 @@ public class BugCollection extends ParseResult { } } + @Override + public boolean equals(Object obj) { + if (null == obj) { + return false; + } + if (! (obj instanceof BugCollection)) { + return false; + } + BugCollection other = (BugCollection)obj; + + return Util.listsAreEqual(this.m_bugs, other.m_bugs); + } } diff --git a/test/net/jaekl/cfb/db/TableMock.java b/test/net/jaekl/cfb/db/TableMock.java index 1c91115..54fb2d0 100644 --- a/test/net/jaekl/cfb/db/TableMock.java +++ b/test/net/jaekl/cfb/db/TableMock.java @@ -1,7 +1,6 @@ package net.jaekl.cfb.db; import java.util.ArrayList; - import static org.junit.Assert.*; public class TableMock extends Table { @@ -17,7 +16,7 @@ public class TableMock extends Table { this(table.m_name, table.m_columns.toArray(new Column[table.m_columns.size()])); } - protected ArrayList mock_getRows() { return m_rows; } + public ArrayList mock_getRows() { return m_rows; } public boolean mock_hasColumn(Column expectedCol) { for (Column col : m_columns) { @@ -39,4 +38,15 @@ public class TableMock extends Table { } m_rows.add(row); } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder("" + getName() + "={\n"); + for (Row row : m_rows) { + sb.append("" + row + "\n"); + } + sb.append("}"); + return sb.toString(); + } } diff --git a/test/net/jaekl/cfb/db/driver/DbDriverMock.java b/test/net/jaekl/cfb/db/driver/DbDriverMock.java index 75813b9..86ae18c 100644 --- a/test/net/jaekl/cfb/db/driver/DbDriverMock.java +++ b/test/net/jaekl/cfb/db/driver/DbDriverMock.java @@ -8,6 +8,7 @@ import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; @@ -19,6 +20,7 @@ import net.jaekl.cfb.db.SequenceMock; import net.jaekl.cfb.db.Sort; import net.jaekl.cfb.db.Table; import net.jaekl.cfb.db.TableMock; +import net.jaekl.cfb.db.TypeMismatchException; public class DbDriverMock extends DbDriver { @@ -104,8 +106,109 @@ public class DbDriverMock extends DbDriver { assertNotNull(columns); assertNotNull(tables); - // TODO: produce sensible output - return new ArrayList(); + if (1 != tables.length) { + throw new UnsupportedOperationException("DbDriverMock does not (yet) implement table joins"); + } + + Table schemaTable = tables[0]; + TableMock table = m_tables.get(schemaTable.getName()); + if (null == table) { + throw new SQLException("Could not find table \"" + schemaTable.getName() + "\" in schema."); + } + + ArrayList rows = table.mock_getRows(); + if (null != conditions) { + ArrayList filteredRows = new ArrayList(); + for (Row row : rows) { + boolean match = true; + for (Condition condition : conditions) { + try { + if (!conditionSatisfied(condition, row)) { + match = false; + } + } + catch (TypeMismatchException exc) { + throw new SQLException(exc); + } + } + if (match) { + filteredRows.add(row); + } + } + rows = filteredRows; + } + + ArrayList result = new ArrayList(); + + // Now, convert the rows from select(*) to select(specified columns) + for (Row row : rows) { + Object[] values = new Object[columns.length]; + + for (int outIdx = 0; outIdx < columns.length; ++outIdx) { + for (int inIdx = 0; inIdx < row.getNumColumns(); ++inIdx) { + if (row.getColumn(inIdx).getName().equals(columns[outIdx].getName())) { + values[outIdx] = row.getValue(inIdx); + } + } + } + result.add(new Row(columns, values)); + } + + return result; + } + + private boolean aLessThanB(Object a, Object b) + { + if ((null == a) || (null == b)) { + return false; + } + + if (a instanceof Number) { + if (! (b instanceof Number)) { + throw new UnsupportedOperationException("Incompatible types"); + } + Number first = (Number)a; + Number second = (Number)b; + return first.doubleValue() < second.doubleValue(); + } + + if (a instanceof Date) { + if (! (b instanceof Date)) { + throw new UnsupportedOperationException("Incompatible types"); + } + Date first = (Date)a; + Date second = (Date)b; + int comp = first.compareTo(second); + boolean result = (comp < 0); + + return (result); + } + + throw new UnsupportedOperationException("Incompatible types"); + } + + private boolean conditionSatisfied(Condition condition, Row row) throws TypeMismatchException, SQLException + { + for (int idx = 0; idx < row.getNumColumns(); ++idx) { + Column col = row.getColumn(idx); + if (condition.getColumn().equals(col)) { + switch(condition.getOperation()) { + case EQUAL: + return (condition.getValue().equals(row.getValue(idx))); + case GREATER_THAN: + return (aLessThanB(condition.getValue(), row.getValue(idx))); + case LESS_THAN: + return (aLessThanB(row.getValue(idx), condition.getValue())); + case NOT_NULL: + return (null != row.getValue(idx)); + case NULL: + return (null == row.getValue(idx)); + default: + throw new UnsupportedOperationException("This condition operation is not supported by DbDriverMock"); + } + } + } + throw new SQLException("Could not locate column \"" + condition.getColumn().getName() + "\" in TableMock."); } // Returns the number of rows inserted diff --git a/test/net/jaekl/cfb/store/DbStoreTest.java b/test/net/jaekl/cfb/store/DbStoreTest.java new file mode 100644 index 0000000..c4e2668 --- /dev/null +++ b/test/net/jaekl/cfb/store/DbStoreTest.java @@ -0,0 +1,163 @@ +package net.jaekl.cfb.store; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; +import java.util.Date; + +import net.jaekl.cfb.analyze.Analysis; +import net.jaekl.cfb.analyze.MessageMap; +import net.jaekl.cfb.db.CfbSchema; +import net.jaekl.cfb.db.TypeMismatchException; +import net.jaekl.cfb.db.driver.ConnectionMock; +import net.jaekl.cfb.db.driver.DbDriverMock; +import net.jaekl.cfb.xml.MessagesXmlData; +import net.jaekl.cfb.xml.messages.MessageCollection; + +import org.junit.Before; +import org.junit.Test; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class DbStoreTest { + private DbStore m_store; + + @Before + public void setUp() throws SQLException, FileNotFoundException, UnsupportedEncodingException, IOException, SAXException + { + MessageMap msgMap = new MessageMap(); + msgMap.parse(new InputSource(new ByteArrayInputStream(MessagesXmlData.XML.getBytes("UTF-8")))); + + DbDriverMock driver = new DbDriverMock(); + CfbSchema schema = new CfbSchema(driver); + ConnectionMock con = new ConnectionMock(); + + schema.setMessageMap(msgMap); + schema.ensureDbInitialized(con); + + MessageCollection msgColl = new MessageCollection(); + m_store = new DbStore(con, driver, msgColl); + } + + @Test + public void testGetPrior_withNoEntries() throws SQLException, TypeMismatchException { + // First test: getPrior(null) should return null + Analysis actual = m_store.getPrior(null); + assertNull(actual); + + // Second test: getPrior(current) with no data in the DB should return null + String projName = "ProjectName"; + String version = "1.2.3"; + Date start = new Date(1234567890); + Date end = new Date(1234567900); + Analysis current = new Analysis(projName, version); + current.setStart(start); + current.setEnd(end); + actual = m_store.getPrior(current); + assertNull(actual); + } + + @Test + public void testPut_thenGetPrior() throws SQLException, TypeMismatchException { + String projName = "ProjectName"; + String firstVersion = "1.0.1"; + Date firstStart = new Date(100); + Date firstEnd = new Date(200); + Analysis firstAnalysis = new Analysis(projName, firstVersion); + firstAnalysis.setStart(firstStart); + firstAnalysis.setEnd(firstEnd); + + boolean result = m_store.put(firstAnalysis); + assertTrue(result); + + String secondVersion = "1.0.2"; + Date secondStart = new Date(2300); + Date secondEnd = new Date(2400); + Analysis secondAnalysis = new Analysis(projName, secondVersion); + secondAnalysis.setStart(secondStart); + secondAnalysis.setEnd(secondEnd); + + Analysis priorAnalysis = m_store.getPrior(secondAnalysis); + assertNotNull(priorAnalysis); + assertEquals(firstAnalysis.getProjectName(), priorAnalysis.getProjectName()); + assertEquals(firstAnalysis.getBuildNumber(), priorAnalysis.getBuildNumber()); + assertEquals(firstAnalysis.getStart(), priorAnalysis.getStart()); + assertEquals(firstAnalysis.getEnd(), priorAnalysis.getEnd()); + assertEquals(firstAnalysis.getBugCollection(), priorAnalysis.getBugCollection()); + } +/* + @Test + public void testGetBugType() { + fail("Not yet implemented"); + } + + @Test + public void testGetCategoryName() { + fail("Not yet implemented"); + } + + @Test + public void testGetLoc() { + fail("Not yet implemented"); + } + + @Test + public void testGetLocId() { + fail("Not yet implemented"); + } + + @Test + public void testFindLocId() { + fail("Not yet implemented"); + } + + @Test + public void testStoreLoc() { + fail("Not yet implemented"); + } + + @Test + public void testGetVarIdBugInstance() { + fail("Not yet implemented"); + } + + @Test + public void testGetVar() { + fail("Not yet implemented"); + } + + @Test + public void testGetVarIdLocalVariable() { + fail("Not yet implemented"); + } + + @Test + public void testFindVarId() { + fail("Not yet implemented"); + } + + @Test + public void testStoreVar() { + fail("Not yet implemented"); + } + + @Test + public void testGetPriorId() { + fail("Not yet implemented"); + } + + @Test + public void testGetAnalysis() { + fail("Not yet implemented"); + } + + @Test + public void testGetBugCollection() { + fail("Not yet implemented"); + } +*/ +} diff --git a/test/net/jaekl/cfb/util/UtilTest.java b/test/net/jaekl/cfb/util/UtilTest.java new file mode 100644 index 0000000..beaa926 --- /dev/null +++ b/test/net/jaekl/cfb/util/UtilTest.java @@ -0,0 +1,69 @@ +package net.jaekl.cfb.util; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +public class UtilTest { + @Test + public void testListsAreEqual() + { + Object[][][] equal = { + { + null, + null + }, + { + { "one", "two", "three" }, + { "one", "two", "three" } + }, + { + { Integer.valueOf(1), Integer.valueOf(2048), Integer.valueOf(3) }, + { Integer.valueOf(1), Integer.valueOf(2048), Integer.valueOf(3) } + } + }; + Object[][][] unequal = { + { + null, + { "one", "two", "three" } + }, + { + { "1", "1", "2" }, + { "1", "2", "1" } + }, + { + { "1", "1", "2" }, + { "1", "2", "2" } + }, + { + { "1", "1", "2" }, + { "1", "2" } + }, + { + { "1", "2", "3" }, + { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) } + }, + { + { Integer.valueOf(1), Integer.valueOf(2048), Integer.valueOf(3) }, + { Integer.valueOf(1), Integer.valueOf(2048), Long.valueOf(3) } + } + }; + + for (Object[][] lists : equal) { + List a = (null == lists[0]) ? null : Arrays.asList(lists[0]); + List b = (null == lists[1]) ? null : Arrays.asList(lists[1]); + boolean result = Util.listsAreEqual(a, b); + assertTrue(result); + } + + for (Object[][] lists : unequal) { + List a = (null == lists[0]) ? null : Arrays.asList(lists[0]); + List b = (null == lists[1]) ? null : Arrays.asList(lists[1]); + boolean result = Util.listsAreEqual(a, b); + assertFalse(result); + } + } +} -- 2.39.2