From 39f6e10710e5ca0cfbffff596afdebfc04426051 Mon Sep 17 00:00:00 2001 From: Julia McCauley Date: Fri, 16 Apr 2021 11:17:54 -0400 Subject: intermediate commit to pull new changes --- src/test/java/edu/brown/cs/student/DBQuerierTest.java | 2 +- src/test/java/edu/brown/cs/student/HubRankTest.java | 2 +- src/test/java/edu/brown/cs/student/LinkMapperTest.java | 2 +- .../java/edu/brown/cs/student/SuspicionRankerTest.java | 14 ++++++++------ 4 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src/test/java') diff --git a/src/test/java/edu/brown/cs/student/DBQuerierTest.java b/src/test/java/edu/brown/cs/student/DBQuerierTest.java index b3ea140..4bcb193 100644 --- a/src/test/java/edu/brown/cs/student/DBQuerierTest.java +++ b/src/test/java/edu/brown/cs/student/DBQuerierTest.java @@ -54,7 +54,7 @@ public class DBQuerierTest { @Test public void testEmptyDatabase(){ try{ - db = new DatabaseQuerier("data/empty.sqlite3"); + db = new DatabaseQuerier("data/testing/empty.sqlite3"); } catch(Exception e){ System.out.println("DBQuerier Test, couldn't connect to db???"); } diff --git a/src/test/java/edu/brown/cs/student/HubRankTest.java b/src/test/java/edu/brown/cs/student/HubRankTest.java index cbe6112..60376dd 100644 --- a/src/test/java/edu/brown/cs/student/HubRankTest.java +++ b/src/test/java/edu/brown/cs/student/HubRankTest.java @@ -41,7 +41,7 @@ public class HubRankTest { @Test public void testEmptyDB(){ try{ - db = new DatabaseQuerier("data/empty.sqlite3"); + db = new DatabaseQuerier("data/testing/empty.sqlite3"); } catch(Exception e){ System.out.println("DBQuerier Test, couldn't connect to db???"); } diff --git a/src/test/java/edu/brown/cs/student/LinkMapperTest.java b/src/test/java/edu/brown/cs/student/LinkMapperTest.java index 3d4bedc..643f63a 100644 --- a/src/test/java/edu/brown/cs/student/LinkMapperTest.java +++ b/src/test/java/edu/brown/cs/student/LinkMapperTest.java @@ -76,7 +76,7 @@ public class LinkMapperTest { @Test public void testEmptyDB(){ try{ - db = new DatabaseQuerier("data/empty.sqlite3"); + db = new DatabaseQuerier("data/testing/empty.sqlite3"); } catch(Exception e){ System.out.println("DBQuerier Test, couldn't connect to db???"); } diff --git a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java index d641507..bc9b339 100644 --- a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java +++ b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java @@ -10,8 +10,11 @@ import org.junit.Before; import org.junit.Test; import java.time.Instant; +import java.util.Comparator; import java.util.List; import java.util.Map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class SuspicionRankerTest { @@ -27,7 +30,7 @@ public class SuspicionRankerTest { @Before public void setUp() { try{ - db = new DatabaseQuerier("data/trades.sqlite3"); + db = new DatabaseQuerier("data/lil_mock.sqlite3"); } catch(Exception e){ System.out.println("DBQuerier Test, couldn't connect to db???"); } @@ -39,14 +42,13 @@ public class SuspicionRankerTest { } @Test - public void testMapper(){ + public void testSuspicionRanksOnMockData(){ setUp(); SuspicionRanker r = new SuspicionRanker(db); List him = r.getSuspicionScoreList(start, end); - //System.out.println(him); - for(Holder guy: him){ - System.out.println(guy.getName() + " " + guy.getSuspicionScore()); - } + assertEquals(6, him.size()); tearDown(); } + + } -- cgit v1.2.3-70-g09d2 From 6dbb2a33ab8bedbdfa5a34ba3c07d86b0dfd2adc Mon Sep 17 00:00:00 2001 From: clarkohw Date: Fri, 16 Apr 2021 11:42:50 -0400 Subject: added trade query endpoint --- .../edu/brown/cs/student/term/DatabaseQuerier.java | 68 +++++++++++++++++----- src/main/java/edu/brown/cs/student/term/Main.java | 19 +++++- .../brown/cs/student/ProfitCalculationTest.java | 56 ++++++++++++++++++ 3 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 src/test/java/edu/brown/cs/student/ProfitCalculationTest.java (limited to 'src/test/java') diff --git a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java index 688270f..6900a19 100644 --- a/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java +++ b/src/main/java/edu/brown/cs/student/term/DatabaseQuerier.java @@ -1,24 +1,29 @@ package edu.brown.cs.student.term; + import edu.brown.cs.student.term.hub.Holder; +import edu.brown.cs.student.term.profit.ProfitCalculation; import edu.brown.cs.student.term.trade.Trade; import java.sql.*; import java.time.Instant; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; public class DatabaseQuerier { private static Connection conn = null; //TODO: Be prepared to overhaul this to account for IDs + /** * Makes a database querier for a particular sqlite database + * * @param filename - String representing filepath of database * @throws SQLException * @throws ClassNotFoundException */ public DatabaseQuerier(String filename) throws SQLException, - ClassNotFoundException { + ClassNotFoundException { Class.forName("org.sqlite.JDBC"); String urlToDB = "jdbc:sqlite:" + filename; // AutoClosable TRY-WITH-RESOURCES ensures database connection will be closed when it is done @@ -31,8 +36,9 @@ public class DatabaseQuerier { /** * Gets the names of all stocks traded between start and end ddate + * * @param startDate - the start date - * @param endDate - the end date + * @param endDate - the end date * @return a list of stock names * @throws SQLException */ @@ -40,13 +46,13 @@ public class DatabaseQuerier { List stocks = new ArrayList<>(); PreparedStatement prep = conn.prepareStatement( - "SELECT DISTINCT stock_name FROM trades WHERE trade_timestamp <= ? AND trade_timestamp >= ?"); + "SELECT DISTINCT stock_name FROM trades WHERE trade_timestamp <= ? AND trade_timestamp >= ?"); prep.setLong(1, endDate.toEpochMilli()); prep.setLong(2, startDate.toEpochMilli()); ResultSet rs = prep.executeQuery(); - while(rs.next()){ + while (rs.next()) { stocks.add(rs.getString(1)); } @@ -57,16 +63,18 @@ public class DatabaseQuerier { /** * Gets all the trades in by stock and buy type, ordered by time + * * @param startDate - the start date of these trades - * @param endDate - the end date of these trades + * @param endDate - the end date of these trades * @return a list of list of trades as specified above * @throws SQLException - if something goes wrong with connection */ - public List> getAllTradesByStock(Instant startDate, Instant endDate) throws SQLException { + public List> getAllTradesByStock(Instant startDate, Instant endDate) + throws SQLException { List> allTrades = new ArrayList<>(); List stocks = getRecentStocks(startDate, endDate); //get the buys and sells for each stock - for(String stock: stocks){ + for (String stock : stocks) { allTrades.add(getTradeByStock(stock, 1, startDate, endDate)); allTrades.add(getTradeByStock(stock, 0, startDate, endDate)); } @@ -75,19 +83,21 @@ public class DatabaseQuerier { /** * Gets a single stock's list of trades for that time period (either buy or sell) - * @param stock - string name of the stock to get the trades for - * @param isBuy - integer whether it's a buy or sell + * + * @param stock - string name of the stock to get the trades for + * @param isBuy - integer whether it's a buy or sell * @param startDate - an Instant representing the start of the time period - * @param endDate - an Instant representing the end of the time period + * @param endDate - an Instant representing the end of the time period * @return - a list of trades for that stock * @throws SQLException - if issue getting connection */ - public List getTradeByStock(String stock, int isBuy, Instant startDate, Instant endDate) throws SQLException{ + public List getTradeByStock(String stock, int isBuy, Instant startDate, Instant endDate) + throws SQLException { List trades = new ArrayList<>(); PreparedStatement prep = conn.prepareStatement( - "SELECT * FROM trades WHERE (stock_name = ? AND is_buy = ?) " - + "AND (trade_timestamp <= ? AND trade_timestamp >= ?) ORDER BY trade_timestamp"); + "SELECT * FROM trades WHERE (stock_name = ? AND is_buy = ?) " + + "AND (trade_timestamp <= ? AND trade_timestamp >= ?) ORDER BY trade_timestamp"); prep.setString(1, stock); prep.setInt(2, isBuy); @@ -95,7 +105,7 @@ public class DatabaseQuerier { prep.setLong(4, startDate.toEpochMilli()); ResultSet rs = prep.executeQuery(); - while(rs.next()){ + while (rs.next()) { trades.add(new Trade(rs.getInt(1), rs.getString(2), rs.getLong(4), rs.getInt(5), rs.getInt(6), new Holder(rs.getInt(7), rs.getString(3)), @@ -107,4 +117,34 @@ public class DatabaseQuerier { return trades; } + + public List getAllTradesByHolder(String person, Date startDate, Date endDate) { + LinkedList trades = new LinkedList<>(); + + try { + PreparedStatement prep; + prep = + conn.prepareStatement("SELECT * FROM \'trades\' WHERE holder_name= ? " + + " AND trade_timestamp BETWEEN ? AND ?" + + "order by trade_timestamp asc;"); + prep.setString(1, person); + prep.setDate(2, startDate); + prep.setDate(3, endDate); + ResultSet rs = prep.executeQuery(); + + while (rs.next()) { + trades.addFirst(new Trade(rs.getInt("trade_id"), + rs.getString("stock_name"), + rs.getDouble("trade_timestamp"), + rs.getInt("is_buy"), + rs.getInt("number_of_shares"), + new Holder(rs.getInt("holder_id"), rs.getString("holder_name")), + rs.getDouble("share_price"))); + } + prep.close(); + } catch (SQLException e) { + System.out.println("ERROR: sql error getting trades"); + } + return trades; + } } diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java index 3b0a258..4238f67 100644 --- a/src/main/java/edu/brown/cs/student/term/Main.java +++ b/src/main/java/edu/brown/cs/student/term/Main.java @@ -9,6 +9,7 @@ import edu.brown.cs.student.term.repl.Command; import edu.brown.cs.student.term.repl.REPL; import edu.brown.cs.student.term.repl.commands.LoadCommand; import edu.brown.cs.student.term.repl.commands.SetupCommand; +import edu.brown.cs.student.term.trade.Trade; import joptsimple.OptionParser; import joptsimple.OptionSet; @@ -121,6 +122,7 @@ public final class Main { Spark.before((request, response) -> response.header("Access-Control-Allow-Origin", "*")); Spark.post("/data", new SuspicionRankHandler()); Spark.post("/profit", new ProfitQueryHandler()); + Spark.post("/trade-lookup", new TradeQueryHandler()); } /** @@ -169,7 +171,6 @@ public final class Main { double sp500PercentGain = 0.0; double sp500Gain = 0.0; try { - DatabaseQuerier db = new DatabaseQuerier("data/trades.sqlite3"); profit = new ProfitCalculation(DatabaseQuerier.getConn(), person, startPeriod, endPeriod); holdings = profit.getHoldingsList(); @@ -194,6 +195,22 @@ public final class Main { } + private static class TradeQueryHandler implements Route { + @Override + public Object handle(Request request, Response response) throws Exception { + JSONObject req = new JSONObject(request.body()); + String person = req.getString("person"); + Date startPeriod = new Date(req.getLong("startTime")); + Date endPeriod = new Date(req.getLong("endTime")); + + DatabaseQuerier db = SetupCommand.getDq(); + List trades = db.getAllTradesByHolder(person, startPeriod, endPeriod); + + return GSON.toJson(trades); + + } + } + /** * Display an error page when an exception occurs in the server. */ diff --git a/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java new file mode 100644 index 0000000..99500d0 --- /dev/null +++ b/src/test/java/edu/brown/cs/student/ProfitCalculationTest.java @@ -0,0 +1,56 @@ +package edu.brown.cs.student; + +import edu.brown.cs.student.term.DatabaseQuerier; +import edu.brown.cs.student.term.hub.Holder; +import edu.brown.cs.student.term.hub.HubSearch; +import edu.brown.cs.student.term.hub.LinkMapper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.time.Instant; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ProfitCalculationTest { + + /** these should span the entire mock dataset */ + //12 am on 3/11 in UTC + private Instant start = Instant.parse("2021-03-11T05:00:00.00Z"); + //12 am on 3/28 in UTC + private Instant end = Instant.parse("2021-03-28T05:00:00.00Z"); + + private DatabaseQuerier db; + + @Before + public void setUp() { + try{ + db = new DatabaseQuerier("data/lil_mock.sqlite3"); + } catch(Exception e){ + System.out.println("DBQuerier Test, couldn't connect to db???"); + } + } + + @After + public void tearDown() { + db = null; + } + + @Test + public void testEmptyDB(){ + try{ + db = new DatabaseQuerier("data/empty.sqlite3"); + } catch(Exception e){ + System.out.println("DBQuerier Test, couldn't connect to db???"); + } + LinkMapper lm = new LinkMapper(db); + HubSearch hub = new HubSearch(lm); + Map hubRanks = hub.runHubSearch(start, end); + assertTrue(hubRanks.isEmpty()); + tearDown(); + } + + +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 6feeaca9f4c12ee90bbf86fe21a45b64cfc4983d Mon Sep 17 00:00:00 2001 From: Julia McCauley Date: Fri, 16 Apr 2021 12:30:09 -0400 Subject: need to pull profit map changes --- .../java/edu/brown/cs/student/term/hub/Holder.java | 24 +++++++++++- .../edu/brown/cs/student/term/hub/HubSearch.java | 3 ++ .../brown/cs/student/term/hub/SuspicionRanker.java | 23 +++++++++++- .../java/edu/brown/cs/student/HubRankTest.java | 43 +++++++++++++++++++--- .../edu/brown/cs/student/SuspicionRankerTest.java | 7 +++- 5 files changed, 88 insertions(+), 12 deletions(-) (limited to 'src/test/java') diff --git a/src/main/java/edu/brown/cs/student/term/hub/Holder.java b/src/main/java/edu/brown/cs/student/term/hub/Holder.java index 66def5e..47a20ae 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/Holder.java +++ b/src/main/java/edu/brown/cs/student/term/hub/Holder.java @@ -1,15 +1,17 @@ package edu.brown.cs.student.term.hub; -import java.util.Objects; +import java.util.*; public class Holder { private int id; private String name; private double suspicionScore; + private Set followers; public Holder(int id, String name){ this.id = id; this.name = name; + followers = new HashSet<>(); } public int getId() { @@ -26,11 +28,29 @@ public class Holder { return name; } + public Set getFollowers() { + return followers; + } + + public void addFollower(Holder follower){ + followers.add(follower); + } + @Override - public String toString() { + public String toString() { return name; } + + public String toTestString() { + return "Holder{" + + "id=" + id + + ", name='" + name + '\'' + + ", suspicionScore=" + suspicionScore + + ", followers=" + followers + + '}'; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java b/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java index ccefeef..86b883f 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java +++ b/src/main/java/edu/brown/cs/student/term/hub/HubSearch.java @@ -60,9 +60,12 @@ public class HubSearch { who followed leader or the number of people who follower followed --- probably the second option ;( */ Set peopleFollowed = followerToLeaderMap.get(follower); + int numberFollowed = peopleFollowed.size(); if(peopleFollowed.contains(leader)){ + //constructs the leader to follower links as we go for use later on + leader.addFollower(follower); return ((damp / numHolders) + (1 - damp) * (1.0 / numberFollowed)); } else if(numberFollowed == 0){ return ((damp / numHolders) + (1 - damp) * (1.0 / numHolders)); diff --git a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java index 9f5f9c1..b52a47a 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java +++ b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java @@ -17,8 +17,11 @@ public class SuspicionRanker { public > V getMaxOfMap(Map map) { Map.Entry maxEntry = Collections.max(map.entrySet(), Map.Entry.comparingByValue()); - System.out.println(maxEntry); - System.out.println(maxEntry.getValue()); + return maxEntry.getValue(); + } + + public > V getMinOfMap(Map map) { + Map.Entry maxEntry = Collections.min(map.entrySet(), Map.Entry.comparingByValue()); return maxEntry.getValue(); } @@ -36,11 +39,27 @@ public class SuspicionRanker { Map profitMap = pc.getProfitMap(); double profitMax = getMaxOfMap(profitMap); + /*if all of our values are negative, we need to flip sides so that the + * biggest loser doesn't end up being the most suspicious person*/ + if(profitMax <= 0) { + profitMax = Math.abs(getMinOfMap(profitMap)); + } + + System.out.println("the max: " + profitMax); + + /*if both the min we found and max we found are 0, then we have + the special case where all the values are 0, in which case we + need to avoid dividing by 0*/ + if(profitMax == 0){ + profitMax = 1; + } + double hubMax = getMaxOfMap(holderToHubScore); for (Holder guy : holderToHubScore.keySet()) { double normalizedProfitScore = profitMap.get(guy.getId()) / profitMax; + double normalizedHubScore = holderToHubScore.get(guy) / hubMax; double suspicionScore = normalizedHubScore * 0.6 + normalizedProfitScore * 0.4; guy.setSuspicionScore(suspicionScore); diff --git a/src/test/java/edu/brown/cs/student/HubRankTest.java b/src/test/java/edu/brown/cs/student/HubRankTest.java index 60376dd..07fd282 100644 --- a/src/test/java/edu/brown/cs/student/HubRankTest.java +++ b/src/test/java/edu/brown/cs/student/HubRankTest.java @@ -24,6 +24,13 @@ public class HubRankTest { private DatabaseQuerier db; + Holder don = new Holder(1, "Don"); + Holder mitch = new Holder(2, "Mitch"); + Holder nancy = new Holder(3, "Nancy"); + Holder midge = new Holder(4, "Midge"); + Holder bob = new Holder(5, "Bob"); + Holder jane = new Holder(6, "Jane"); + @Before public void setUp() { try{ @@ -79,12 +86,6 @@ public class HubRankTest { LinkMapper lm = new LinkMapper(db); HubSearch hub = new HubSearch(lm); Map hubRanks = hub.runHubSearch(start, end); - Holder don = new Holder(1, "Don"); - Holder mitch = new Holder(2, "Mitch"); - Holder nancy = new Holder(3, "Nancy"); - Holder midge = new Holder(4, "Midge"); - Holder bob = new Holder(5, "Bob"); - Holder jane = new Holder(6, "Jane"); System.out.println(hubRanks); assertTrue(hubRanks.get(mitch) > hubRanks.get(don)); @@ -95,4 +96,34 @@ public class HubRankTest { tearDown(); } + + @Test + public void testFollowersSetsConstructedProperly(){ + setUp(); + LinkMapper lm = new LinkMapper(db); + HubSearch hub = new HubSearch(lm); + Map hubRanks = hub.runHubSearch(start, end); + + for(Holder person: hubRanks.keySet()){ + System.out.println(person.getName() + " " + person.getFollowers()); + if(person.equals(don)) { + assertTrue(person.getFollowers().contains(mitch)); + assertTrue(person.getFollowers().contains(nancy)); + assertTrue(person.getFollowers().contains(midge)); + } else if(person.equals(bob)){ + assertTrue(person.getFollowers().contains(mitch)); + assertTrue(person.getFollowers().contains(midge)); + } else if(person.equals(nancy)){ + assertTrue(person.getFollowers().contains(midge)); + } else if(person.equals(mitch)){ + assertTrue(person.getFollowers().contains(nancy)); + assertTrue(person.getFollowers().contains(midge)); + assertTrue(person.getFollowers().contains(bob)); + } else{ + assertTrue(person.getFollowers().isEmpty()); + } + } + + tearDown(); + } } diff --git a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java index bc9b339..266eb30 100644 --- a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java +++ b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java @@ -30,7 +30,7 @@ public class SuspicionRankerTest { @Before public void setUp() { try{ - db = new DatabaseQuerier("data/lil_mock.sqlite3"); + db = new DatabaseQuerier("data/trades.sqlite3"); } catch(Exception e){ System.out.println("DBQuerier Test, couldn't connect to db???"); } @@ -46,7 +46,10 @@ public class SuspicionRankerTest { setUp(); SuspicionRanker r = new SuspicionRanker(db); List him = r.getSuspicionScoreList(start, end); - assertEquals(6, him.size()); + //assertEquals(6, him.size()); + for(Holder p: him){ + System.out.println(p.toTestString()); + } tearDown(); } -- cgit v1.2.3-70-g09d2 From 12ebe0f16988daf87391f99f681a3e89b985d8c2 Mon Sep 17 00:00:00 2001 From: Julia McCauley Date: Fri, 16 Apr 2021 12:33:16 -0400 Subject: added followers field to holder objects, continued writing tests --- src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java | 2 -- src/test/java/edu/brown/cs/student/SuspicionRankerTest.java | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src/test/java') diff --git a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java index b52a47a..fd214fd 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java +++ b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java @@ -45,8 +45,6 @@ public class SuspicionRanker { profitMax = Math.abs(getMinOfMap(profitMap)); } - System.out.println("the max: " + profitMax); - /*if both the min we found and max we found are 0, then we have the special case where all the values are 0, in which case we need to avoid dividing by 0*/ diff --git a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java index 266eb30..cb0fc6f 100644 --- a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java +++ b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java @@ -30,7 +30,7 @@ public class SuspicionRankerTest { @Before public void setUp() { try{ - db = new DatabaseQuerier("data/trades.sqlite3"); + db = new DatabaseQuerier("data/lil_mock.sqlite3"); } catch(Exception e){ System.out.println("DBQuerier Test, couldn't connect to db???"); } @@ -46,7 +46,7 @@ public class SuspicionRankerTest { setUp(); SuspicionRanker r = new SuspicionRanker(db); List him = r.getSuspicionScoreList(start, end); - //assertEquals(6, him.size()); + assertEquals(6, him.size()); for(Holder p: him){ System.out.println(p.toTestString()); } -- cgit v1.2.3-70-g09d2 From 7cf6c2b08add62df693a9a2e2c1ee0ed6f0c5aee Mon Sep 17 00:00:00 2001 From: Julia McCauley Date: Fri, 16 Apr 2021 16:33:28 -0400 Subject: finished up unit tests and added system tests --- cs32-test | 222 +++++++++++++++++++++ src/main/java/edu/brown/cs/student/term/Main.java | 2 + .../brown/cs/student/term/hub/SuspicionRanker.java | 26 ++- .../cs/student/term/repl/commands/RankCommand.java | 48 +++++ .../edu/brown/cs/student/SuspicionRankerTest.java | 38 +++- tests/student/hub/data_not_loaded.test | 5 + tests/student/hub/fake_command.test | 5 + tests/student/hub/load_data.test | 5 + tests/student/hub/no_trades_for_dates.test | 6 + tests/student/hub/rank_lil_mock.test | 12 ++ tests/student/hub/too_many_arguments_setup.test | 5 + 11 files changed, 365 insertions(+), 9 deletions(-) create mode 100755 cs32-test create mode 100644 src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java create mode 100644 tests/student/hub/data_not_loaded.test create mode 100644 tests/student/hub/fake_command.test create mode 100644 tests/student/hub/load_data.test create mode 100644 tests/student/hub/no_trades_for_dates.test create mode 100644 tests/student/hub/rank_lil_mock.test create mode 100644 tests/student/hub/too_many_arguments_setup.test (limited to 'src/test/java') diff --git a/cs32-test b/cs32-test new file mode 100755 index 0000000..8ca6a99 --- /dev/null +++ b/cs32-test @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 + +from optparse import OptionParser +import difflib, io, os, subprocess, sys, tempfile, codecs +sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf8', buffering=1) + +# color constants +END = '\033[0m' +HEADER = '\033[48;5;60m\033[38;5;15m' +TITLE = '\033[1;30m' +FAIL = '\033[0;31m' +CRASH = '\033[1;31m' +SEP = '\033[0;37m' +PASS = '\033[0;32m' +GOOD = '\033[38;5;107m' +BAD = '\033[38;5;52m' + +DIVIDER_WIDTH = 80 + +# Section tags in the test case +ARGS_TAG = 'ARGS' +INPUT_TAG = 'INPUT' +OUTPUT_TAG = 'OUTPUT' +SECTION_TAGS = [ARGS_TAG, INPUT_TAG, OUTPUT_TAG] +END_TAG = 'END' + +# Message prefixes in the program's output +ERROR_PREFIX = 'ERROR:' +IGNORE_PREFIX = 'INFO:' + +differ = difflib.Differ() + +class TestFormatError(Exception): + """Raised when a test file contains syntax errors.""" + def __init__(self, message): + self.message = message + + +class Test: + """An individual test case.""" + def __init__(self, executable, filepath, ignore_whitespace=False): + if not os.path.isfile(executable): + print('Executable', executable, 'does not exist.') + sys.exit(1) + + self.executable = executable + + if not os.path.isfile(filepath): + print('Test ', filepath, 'does not exist.') + sys.exit(1) + + self.filename = os.path.basename(filepath) + if self.filename.find('.') >= 0: + self.name = self.filename[:self.filename.index('.')] + + try: + sections = self.readtest(filepath, ignore_whitespace) + except TestFormatError as tfe: + print('Failed to parse "{0}": {1}'.format(filepath, tfe.message)) + self.valid = False + return + + self.valid = True + self.args = sections[ARGS_TAG] + self.input = sections[INPUT_TAG] + self.expected = sections[OUTPUT_TAG] + + self.command = self.executable + ' ' + self.args + if self.executable[-4:] == '.jar': + self.command = 'java -jar ' + self.command + self.description = self.command + + @staticmethod + def readtest(filename, ignore_whitespace=False): + """Extract the ARGS, INPUT, and OUTPUT sections from a test file. + + Returns a dict with: + ARGS_TAG -> str + INPUT_TAG -> list of str + OUTPUT_TAG -> list of str + """ + sections = { + ARGS_TAG: [], + INPUT_TAG: [], + OUTPUT_TAG: [] + } + current_section = None + + with open(filename, 'r', encoding = "utf-8") as file: + lines = [line for line in file.readlines()] + + for idx, line in enumerate(lines): + if line.startswith(END_TAG): + current_section = END_TAG + break + elif any(line.startswith(tag) for tag in SECTION_TAGS): + current_section = line.rstrip() + else: + sections[current_section].append(line.rstrip('\n') if not ignore_whitespace else line.rstrip()) + + # print(sections) + + if len(sections[ARGS_TAG]) > 1: + raise TestFormatError('All arguments under the ARGS tag must be on the same line.') + elif len(sections[ARGS_TAG]) == 1: + sections[ARGS_TAG] = sections[ARGS_TAG][0] + else: + sections[ARGS_TAG] = "" + + if current_section != END_TAG: + raise TestFormatError('No END tag found. Every test file must contain an END tag.') + + return sections + + # For collapsing consecutive "ERROR: ..." lines down into one line + @staticmethod + def collapse_errors(lines): + collapsed_lines = [] + prev_was_error = False + for line in lines: + if not line.startswith("ERROR:"): + collapsed_lines.append(line) + prev_was_error = False + elif line.startswith("ERROR:") and not prev_was_error: + collapsed_lines.append("ERROR:") + prev_was_error = True + else: # Line is an error line, but we already added the previous line as an error + continue + return collapsed_lines + + @staticmethod + def acceptable(expected, actual): + expected = Test.collapse_errors(expected) + actual = Test.collapse_errors(actual) + if len(expected) != len(actual): + return False + for expected_line, actual_line in zip(expected, actual): + if expected_line != actual_line: + if not expected_line.startswith("ERROR:") or not actual_line.startswith("ERROR:"): + return False + return True + + """ + Runs the test. Returns True if passed, False otherwise. + + @param timeout time limit on waiting for a response from the student + executable (in seconds) + """ + def run(self, timeout, ignore_whitespace): + print('Running ' + self.name) + + with tempfile.NamedTemporaryFile(mode='w+',encoding="utf-8") as temp: + with tempfile.NamedTemporaryFile(mode='r+',encoding ="utf-8") as input: + for line in self.input: + print(line, file=input) + input.seek(0) + + try: + cp = subprocess.call(["sh", "-c", self.command], timeout=timeout, + stdin=input, stdout=temp, stderr=subprocess.STDOUT) + except subprocess.TimeoutExpired: + print("%s timed out after %f seconds" % (self.name, timeout)) + self.passed = False + return self.passed + temp.seek(0) + + actual = [] + for line in temp: + if not line.startswith(IGNORE_PREFIX): + actual.append(line.rstrip('\n') if not ignore_whitespace else line.rstrip()) + + passed = Test.acceptable(self.expected, actual) + if not passed: + # Only diff if the test failed (diff'ing will almost always point out ERROR tags) + for line in differ.compare(self.expected, actual): + if line[0:2] != ' ': + print(line) + + print('Result: ', (PASS + 'Passed' if passed else FAIL + 'Failed'), END) + print(SEP + ('-' * DIVIDER_WIDTH) + END) + + self.passed = passed + return self.passed + + +if __name__ == '__main__': + parser = OptionParser() + parser.add_option('-t', '--timeout', dest='timeout', + help=("The timeout (in seconds) to use when waiting for each test to run." + " 5 by default."), type='float', default=5.0) + parser.add_option('-e', '--executable', dest='executable', + help=("The executable to test. `./run` by default."), + type='string', default='./run') + parser.add_option('-i', '--ignore-whitespace', action='store_true', dest='ignore_whitespace', + help=("Whether or not to ignore trailing whitespace. False (not ignored) by default."), + default=False) + (opts, args) = parser.parse_args() + + if len(args) < 1: + print('Usage:', sys.argv[0], ' [ ...]') + print('Run `', sys.argv[0], ' --help` for more options.') + sys.exit(1) + + executable = opts.executable + tests = [Test(executable, arg, opts.ignore_whitespace) for arg in args] + + passed = 0 + for test in tests: + if test.valid: + test.run(opts.timeout, opts.ignore_whitespace) + if test.passed: + passed += 1 + + print(END) + + # Print summary + print(str(passed), '/', str(len(tests)), 'tests passed' + END) + + if passed == len(tests): + print(PASS + 'TEST SUITE PASSED' + END) + else: + print(FAIL + 'TEST SUITE FAILED' + END) diff --git a/src/main/java/edu/brown/cs/student/term/Main.java b/src/main/java/edu/brown/cs/student/term/Main.java index 5c7497f..1c9b1f7 100644 --- a/src/main/java/edu/brown/cs/student/term/Main.java +++ b/src/main/java/edu/brown/cs/student/term/Main.java @@ -8,6 +8,7 @@ import edu.brown.cs.student.term.hub.SuspicionRanker; import edu.brown.cs.student.term.repl.Command; import edu.brown.cs.student.term.repl.REPL; import edu.brown.cs.student.term.repl.commands.LoadCommand; +import edu.brown.cs.student.term.repl.commands.RankCommand; import edu.brown.cs.student.term.repl.commands.SetupCommand; import edu.brown.cs.student.term.trade.Trade; import joptsimple.OptionParser; @@ -88,6 +89,7 @@ public final class Main { HashMap commandHashMap = new HashMap<>(); commandHashMap.put("setup", new SetupCommand()); commandHashMap.put("load", new LoadCommand()); + commandHashMap.put("rank", new RankCommand()); /** add commands to map here! */ REPL repl = new REPL(commandHashMap); repl.runREPL(); diff --git a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java index 2b567c2..74628aa 100644 --- a/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java +++ b/src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java @@ -15,23 +15,36 @@ public class SuspicionRanker { this.querier = db; } - public > V getMaxOfMap(Map map) { + private > V getMaxOfMap(Map map) { Map.Entry maxEntry = Collections.max(map.entrySet(), Map.Entry.comparingByValue()); return maxEntry.getValue(); } - public > V getMinOfMap(Map map) { + private > V getMinOfMap(Map map) { Map.Entry maxEntry = Collections.min(map.entrySet(), Map.Entry.comparingByValue()); return maxEntry.getValue(); } + private class SuspicionComparator implements Comparator { + @Override + public int compare(Holder o1, Holder o2) { + if (o1.getSuspicionScore() > o2.getSuspicionScore()) { + return -1; + } else if (o1.getSuspicionScore() < o2.getSuspicionScore()) { + return 1; + } else { + return 0; + } + } + } + public List getSuspicionScoreList(Instant start, Instant end) { + PriorityQueue orderedSuspicion = new PriorityQueue<>(new SuspicionComparator()); List suspicionList = new ArrayList<>(); try { LinkMapper lm = new LinkMapper(querier); HubSearch hub = new HubSearch(lm); Map holderToHubScore = hub.runHubSearch(start, end); - System.out.println(holderToHubScore); ProfitCalculation pc = new ProfitCalculation(DatabaseQuerier.getConn(), "", new Date(start.toEpochMilli()), @@ -58,7 +71,6 @@ public class SuspicionRanker { profitMax = 1; } - double hubMax = getMaxOfMap(holderToHubScore); for (Holder guy : holderToHubScore.keySet()) { @@ -67,12 +79,16 @@ public class SuspicionRanker { double normalizedHubScore = holderToHubScore.get(guy) / hubMax; double suspicionScore = normalizedHubScore * 0.6 + normalizedProfitScore * 0.4; guy.setSuspicionScore(suspicionScore); - suspicionList.add(guy); + orderedSuspicion.add(guy); } } catch (Exception e) { e.printStackTrace(); System.out.println("ERROR: Could not connect to database querier"); } + int size = orderedSuspicion.size(); + for(int i = 0; i < size; i++){ + suspicionList.add(orderedSuspicion.poll()); + } return suspicionList; } } diff --git a/src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java b/src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java new file mode 100644 index 0000000..2005f19 --- /dev/null +++ b/src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java @@ -0,0 +1,48 @@ +package edu.brown.cs.student.term.repl.commands; + +import edu.brown.cs.student.term.DatabaseQuerier; +import edu.brown.cs.student.term.hub.Holder; +import edu.brown.cs.student.term.hub.SuspicionRanker; +import edu.brown.cs.student.term.repl.Command; +import org.json.JSONObject; + +import java.time.Instant; +import java.util.List; + +public class RankCommand implements Command { + + private String error; + + @Override + public String run(String[] args) { + + error = ""; + if(args.length == 2){ + try{ + DatabaseQuerier dq = SetupCommand.getDq(); + DatabaseQuerier db = SetupCommand.getDq(); + SuspicionRanker ranker = new SuspicionRanker(db); + + long startMilli = Long.parseLong(args[0]); + long endMilli = Long.parseLong(args[1]); + Instant start = Instant.ofEpochMilli(startMilli); + Instant end = Instant.ofEpochMilli(endMilli); + List suspiciousHolders = ranker.getSuspicionScoreList(start, end); + for(Holder h: suspiciousHolders){ + System.out.println(h.toString()); + } + } catch (Exception e) { + error = "ERROR: Could not connect to database. Ensure this is a valid database."; + System.out.println(error); + return error; + } + } else { + error = "ERROR: Incorrect number of arguments for suspicion rank command:" + + " rank [start date in epoch milliseconds] [end date in epoch milliseconds]"; + System.out.println(error); + return error; + } + return error; + + } +} diff --git a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java index cb0fc6f..3f8ea3d 100644 --- a/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java +++ b/src/test/java/edu/brown/cs/student/SuspicionRankerTest.java @@ -18,12 +18,18 @@ import static org.junit.Assert.assertTrue; public class SuspicionRankerTest { + // ./cs32-test tests/student/hub/*.test + + //friday april 9th + //private Instant start = Instant.ofEpochMilli(1618004180000L); + + //saturday april 10th + //private Instant end = Instant.ofEpochMilli(1618019436000L); + //12 am on 3/11 in UTC - //private Instant start = Instant.parse("2021-03-11T05:00:00.00Z"); - private Instant start = Instant.ofEpochMilli(161800418000L); + private Instant start = Instant.parse("2021-03-11T05:00:00.00Z"); //12 am on 3/28 in UTC - //private Instant end = Instant.parse("2021-03-28T05:00:00.00Z"); - private Instant end = Instant.ofEpochMilli(1618019436000L); + private Instant end = Instant.parse("2021-03-28T05:00:00.00Z"); private DatabaseQuerier db; @@ -53,5 +59,29 @@ public class SuspicionRankerTest { tearDown(); } + @Test + public void testSusRankEmptyDB(){ + try{ + db = new DatabaseQuerier("data/testing/empty.sqlite3"); + } catch(Exception e){ + System.out.println("DBQuerier Test, couldn't connect to db???"); + } + SuspicionRanker r = new SuspicionRanker(db); + List vals = r.getSuspicionScoreList(start, end); + assertTrue(vals.isEmpty()); + tearDown(); + + } + + @Test + public void testBadDates(){ + setUp(); + SuspicionRanker r = new SuspicionRanker(db); + List vals = r.getSuspicionScoreList(end, start); + assertTrue(vals.isEmpty()); + tearDown(); + + } + } diff --git a/tests/student/hub/data_not_loaded.test b/tests/student/hub/data_not_loaded.test new file mode 100644 index 0000000..f89cb51 --- /dev/null +++ b/tests/student/hub/data_not_loaded.test @@ -0,0 +1,5 @@ +INPUT +rank 1618004180000 1618019436000 +OUTPUT +ERROR: +END \ No newline at end of file diff --git a/tests/student/hub/fake_command.test b/tests/student/hub/fake_command.test new file mode 100644 index 0000000..95c3ee7 --- /dev/null +++ b/tests/student/hub/fake_command.test @@ -0,0 +1,5 @@ +INPUT +fake 1 1 +OUTPUT +ERROR: +END \ No newline at end of file diff --git a/tests/student/hub/load_data.test b/tests/student/hub/load_data.test new file mode 100644 index 0000000..87b6220 --- /dev/null +++ b/tests/student/hub/load_data.test @@ -0,0 +1,5 @@ +INPUT +setup data/trades.sqlite3 +OUTPUT +Connected to db at data/trades.sqlite3 +END \ No newline at end of file diff --git a/tests/student/hub/no_trades_for_dates.test b/tests/student/hub/no_trades_for_dates.test new file mode 100644 index 0000000..dc142ed --- /dev/null +++ b/tests/student/hub/no_trades_for_dates.test @@ -0,0 +1,6 @@ +INPUT +setup data/lil_mock.sqlite3 +rank 161462819800 161696099800 +OUTPUT +Connected to db at data/lil_mock.sqlite3 +END \ No newline at end of file diff --git a/tests/student/hub/rank_lil_mock.test b/tests/student/hub/rank_lil_mock.test new file mode 100644 index 0000000..6af2a7e --- /dev/null +++ b/tests/student/hub/rank_lil_mock.test @@ -0,0 +1,12 @@ +INPUT +setup data/lil_mock.sqlite3 +rank 1614628198000 1616960998000 +OUTPUT +Connected to db at data/lil_mock.sqlite3 +Mitch +Bob +Don +Jane +Nancy +Midge +END \ No newline at end of file diff --git a/tests/student/hub/too_many_arguments_setup.test b/tests/student/hub/too_many_arguments_setup.test new file mode 100644 index 0000000..1ec48fe --- /dev/null +++ b/tests/student/hub/too_many_arguments_setup.test @@ -0,0 +1,5 @@ +INPUT +setup data/trades.sqlite3 more +OUTPUT +ERROR: +END \ No newline at end of file -- cgit v1.2.3-70-g09d2