aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcs32-test222
-rw-r--r--src/main/java/edu/brown/cs/student/term/Main.java2
-rw-r--r--src/main/java/edu/brown/cs/student/term/hub/SuspicionRanker.java26
-rw-r--r--src/main/java/edu/brown/cs/student/term/repl/commands/RankCommand.java48
-rw-r--r--src/test/java/edu/brown/cs/student/SuspicionRankerTest.java38
-rw-r--r--tests/student/hub/data_not_loaded.test5
-rw-r--r--tests/student/hub/fake_command.test5
-rw-r--r--tests/student/hub/load_data.test5
-rw-r--r--tests/student/hub/no_trades_for_dates.test6
-rw-r--r--tests/student/hub/rank_lil_mock.test12
-rw-r--r--tests/student/hub/too_many_arguments_setup.test5
11 files changed, 365 insertions, 9 deletions
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], '<test file> [<test file> ...]')
+ 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<String, Command> 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 <K, V extends Comparable<V>> V getMaxOfMap(Map<K, V> map) {
+ private <K, V extends Comparable<V>> V getMaxOfMap(Map<K, V> map) {
Map.Entry<K, V> maxEntry = Collections.max(map.entrySet(), Map.Entry.comparingByValue());
return maxEntry.getValue();
}
- public <K, V extends Comparable<V>> V getMinOfMap(Map<K, V> map) {
+ private <K, V extends Comparable<V>> V getMinOfMap(Map<K, V> map) {
Map.Entry<K, V> maxEntry = Collections.min(map.entrySet(), Map.Entry.comparingByValue());
return maxEntry.getValue();
}
+ private class SuspicionComparator implements Comparator<Holder> {
+ @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<Holder> getSuspicionScoreList(Instant start, Instant end) {
+ PriorityQueue<Holder> orderedSuspicion = new PriorityQueue<>(new SuspicionComparator());
List<Holder> suspicionList = new ArrayList<>();
try {
LinkMapper lm = new LinkMapper(querier);
HubSearch hub = new HubSearch(lm);
Map<Holder, Double> 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<Holder> 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<Holder> vals = r.getSuspicionScoreList(start, end);
+ assertTrue(vals.isEmpty());
+ tearDown();
+
+ }
+
+ @Test
+ public void testBadDates(){
+ setUp();
+ SuspicionRanker r = new SuspicionRanker(db);
+ List<Holder> 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