diff --git a/gdb-port/tests/unit/ast.py b/gdb-port/tests/unit/ast.py
index fe82ab4757f1253fabdea7e7b466316836bf39fd..49510934fe66ed7b0c63dba6569280ffefdf3f50 100644
--- a/gdb-port/tests/unit/ast.py
+++ b/gdb-port/tests/unit/ast.py
@@ -43,3 +43,36 @@ class HParseResultCreation(unittest.TestCase):
 		gdbv_patcher.stop()
 		self.assertIsNone(tok)
 
+	def test_read_member(self):
+		hpr_pointer_type_patcher = unittest.mock.patch.object(HParseResult, 'HParseResult_t_p', spec=gdb.Type)
+		gdbv_patcher = unittest.mock.patch('gdb.Value', autospec=True)
+		gdb_lookup_type_patcher = unittest.mock.patch.object(gdb, 'lookup_type')
+		hpr_ast_not_null_patcher = unittest.mock.patch.object(HParseResult, 'read_AST_not_null', return_value=True)
+		hpr_make_hparsedtoken_patcher = unittest.mock.patch.object(HParseResult, 'make_HParsedToken', spec=HParsedToken)
+		init_patcher = unittest.mock.patch.object(HParseResult, '__init__', return_value=None)
+
+		hpr_pointer_type_patcher.start()
+		#hpr_ast_not_null_patcher.start()
+		#hpr_make_hparsedtoken_patcher.start()
+		gdbv_mock_object = gdbv_patcher.start()
+		gdb_lookup_type_mock_object = gdb_lookup_type_patcher.start()
+		init_patcher.start()
+
+		result = HParseResult(0xdeadbeef)
+		result.address = 0xdeadbeef
+		member = result.read_member('ast')
+
+		# Check that struct being indexed is at self.address
+		self.assertEqual(gdbv_mock_object.mock_calls[0], unittest.mock.call(0xdeadbeef))
+		# Check that the value returned is parse_result_gdb_value['foo']
+		self.assertEqual(gdbv_mock_object.mock_calls[-1], unittest.mock.call().cast().__getitem__('ast'))
+		# Check that gdb.lookup_type is not called
+		self.assertEqual(gdb_lookup_type_mock_object.mock_calls, [])
+
+	def test_read_member_with_type_lookup(self):
+		gdb_lookup_type_patcher = unittest.mock.patch.object(gdb, 'lookup_type')
+		raise Exception("Not implemented")
+
+	# TODO: should read_member employ a whitelist for member_name, or should this trigger an exeption?
+	def test_read_member_invalid_param(self):
+		raise Exception("Not implemented")
diff --git a/gdb-port/tests/unit/breakpointmanager.py b/gdb-port/tests/unit/breakpointmanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..74941b0b4aa43796877902c61dad8fa5bc6108e6
--- /dev/null
+++ b/gdb-port/tests/unit/breakpointmanager.py
@@ -0,0 +1,169 @@
+import unittest
+import unittest.mock
+
+class BreakpointManagerCreated(unittest.TestCase):
+	def setUp(self):
+		test_breakpoints = [ "init_runlengthdecode_parser", "init_LZW_parser" ]
+		self.bpm = BreakpointManager(test_breakpoints)
+		self.arch = gdb.selected_frame().architecture()
+
+	def test_locate_retqs(self):
+		retqs = self.bpm.locate_retqs("init_runlengthdecode_parser")
+		addr = retqs[0]
+		instr = self.arch.disassemble(addr, addr+8, 1)[0]
+		self.assertTrue(instr['asm'].startswith("ret"))
+
+	def test_locate_retq(self):
+		retq = self.bpm.locate_retq("init_runlengthdecode_parser")
+		instr = self.arch.disassemble(retq, retq+8, 1)[0]
+		self.assertTrue(instr['asm'].startswith("ret"))
+
+
+class BreakpointManagerSettingBreakpoints(unittest.TestCase):
+	def setUp(self):
+		#TODO: make these tests independent of the pdf parser
+		test_breakpoints = [ "init_runlengthdecode_parser", "init_LZW_parser" ]
+		# "constants" like rld_retq below could be moved to setUpClass (possibly better performance)
+		self.bpm = BreakpointManager(test_breakpoints)
+		self.arch = gdb.selected_frame().architecture()
+		# TODO: instead of locate_retq, get bp address from call_list, and disassemble
+		self.rld_retq = self.bpm.locate_retq(test_breakpoints[0])
+		self.lzw_retq = self.bpm.locate_retq(test_breakpoints[1])
+
+		self.hrbp_patcher = unittest.mock.patch('__main__.HRuleBreakpoint')
+		self.hrbp_mock_object = self.hrbp_patcher.start()
+		self.plprbp_patcher = unittest.mock.patch('__main__.PerformLowLevelParseRetBreakpoint')
+		self.plprbp_mock_object = self.plprbp_patcher.start()
+		self.hpprbp_patcher = unittest.mock.patch('__main__.HPackratParseRetBreakpoint')
+		self.hpprbp_mock_object = self.hpprbp_patcher.start()
+		self.pvbp_patcher = unittest.mock.patch('__main__.ParserVirtualBreakpoint')
+		self.pvbp_mock_object = self.pvbp_patcher.start()
+		self.ipbp_patcher = unittest.mock.patch('__main__.InitParserBreakpoint')
+		self.ipbp_mock_object = self.ipbp_patcher.start()
+
+		self.hdpbp_patcher = unittest.mock.patch('__main__.HDoParseBreakpoint')
+		self.hdpbp_mock_object = self.hdpbp_patcher.start()
+		self.hppbp_patcher = unittest.mock.patch('__main__.HPackratParseBreakpoint')
+		self.hppbp_mock_object = self.hppbp_patcher.start()
+		self.pllpbp_patcher = unittest.mock.patch('__main__.PerformLowLevelParseBreakpoint')
+		self.pllpbp_mock_object = self.pllpbp_patcher.start()
+		self.hamrbp_patcher = unittest.mock.patch('__main__.HArenaMallocRawBreakpoint')
+		self.hamrbp_mock_object = self.hamrbp_patcher.start()
+		self.hdprbp_patcher = unittest.mock.patch('__main__.HDoParseRetBreakpoint')
+		self.hdprbp_mock_object = self.hdprbp_patcher.start()
+
+	def tearDown(self):
+		self.hpprbp_patcher.stop()
+		self.plprbp_patcher.stop()
+		self.hrbp_patcher.stop()
+		self.pvbp_patcher.stop()
+		self.ipbp_patcher.stop()
+		self.hdpbp_patcher.stop()
+		self.hppbp_patcher.stop()
+		self.pllpbp_patcher.stop()
+		self.hamrbp_patcher.stop()
+		self.hdprbp_patcher.stop()
+
+# TODO: mock breakpoints, assert on arguments to constructor
+	def test_set_h_rule_breakpoints(self):
+		self.bpm.set_h_rule_breakpoints()
+		self.assertTrue(self.hrbp_mock_object.call_count == 2)
+		#bps_valid = [ bp.is_valid() for bp in self.bpm.h_rule_breakpoints]
+		# Assert that we have as many breakpoints as h_rule_function and is_valid() returns true for all of them
+		#self.assertEqual(bps_valid, len(self.bpm.H_RULE_FUNCTIONS) * [True])
+		# Cleanup
+		#for bp in self.bpm.h_rule_breakpoints:
+		#	bp.delete()
+
+	def test_set_hammer_retq_breakpoints(self):
+		self.bpm.set_hammer_retq_breakpoints()
+		self.assertTrue(self.plprbp_mock_object.called)
+		self.assertTrue(self.hpprbp_mock_object.called)
+		self.assertTrue(self.hdprbp_mock_object.called)
+		#bps_valid = [ bp.is_valid() for bp in self.bpm.hammer_retq_breakpoints]
+		#self.assertEqual(bps_valid, 2 * [True])
+		#Cleanup
+		#for bp in self.bpm.hammer_retq_breakpoints:
+		#	bp.delete()
+
+	def test_del_hammer_retq_breakpoints(self):
+		self.bpm.set_hammer_retq_breakpoints()
+		bps = self.bpm.hammer_retq_breakpoints
+		self.bpm.del_hammer_retq_breakpoints()
+		for bp in bps:
+			self.assertTrue(bp.delete.called)
+		#self.assertTrue(self.plprbp_mock_object.delete.called)
+		#self.assertTrue(self.hpprbp_mock_object.delete.called)
+		#bps_valid = [ bp.is_valid() for bp in bps]
+		#self.assertEqual(bps_valid, 2 * [False])
+
+	@unittest.skip("Need to make a mock binary to find RETQs in for this one")
+	def test_locate_retqs(self):
+		self.fail("Not implemented yet")
+
+	@unittest.skip("Need to make a mock binary to find RETQs in for this one")
+	def test_locate_retq(self):
+		self.fail("Not implemented yet")
+
+	def test_set_parser_virtual_breakpoints(self):
+		self.bpm.set_parser_virtual_breakpoints()
+		method_names = [ a for a in dir(self.bpm) if a.startswith("parse_")]
+		virt_bps = [ self.bpm.__getattribute__(a) for a in dir(self.bpm) if a.startswith("parse_")]
+		self.assertTrue(self.pvbp_mock_object.call_count == len(virt_bps))
+		#print(self.pvbp_mock_object.mock_calls)
+		for i in range(0, len(method_names)):
+			with self.subTest(i=i):
+				self.pvbp_mock_object.assert_any_call(method_names[i])
+
+	def test_del_parser_virtual_breakpoints(self):
+		self.bpm.set_parser_virtual_breakpoints()
+		virt_bps = [ self.bpm.__getattribute__(a) for a in dir(self.bpm) if a.startswith("parse_")]
+		self.bpm.del_parser_virtual_breakpoints()
+		#TODO: in tests, all self.parse_* attributes refer to the same mock object
+		# so we can't just do virt_bps[i].delete.assert_called_once(), because it
+		# will have been called len(virt_bps) times.
+		# it would be desirable to have separate mock object instances,
+		# or for mock_object to know which attribute it has
+		# been called through, to allow checking individual self.parse_*
+		# breakpoints for deletion
+		self.assertTrue(len(virt_bps) > 0)
+		self.assertTrue(virt_bps[0].mock_calls == [unittest.mock.call.delete()] * len(virt_bps))
+		#self.assertTrue(virt_bps[0].delete.call_count == len(virt_bps))
+		#for i in range(0, len(virt_bps)):
+		#	with self.subTest(i=i):
+		#		virt_bps[i].delete.assert_called_once()
+
+	def test_set_init_parser_breakpoint(self):
+		self.bpm.set_init_parser_breakpoint()
+		#print(self.ipbp_mock_object.call_args.args)
+		gdb_addr_expr = self.ipbp_mock_object.call_args.args[0]
+		bp_addr = int(gdb_addr_expr.strip('*'), 16)
+		#TODO: probably could just get the machine word size
+		instr = self.arch.disassemble(bp_addr, bp_addr+8, 1)[0]
+		self.assertTrue(instr['asm'].startswith('ret'))
+
+	def test_del_init_parser_breakpoints(self):
+		self.bpm.set_init_parser_breakpoint()
+		self.bpm.del_init_parser_breakpoint()
+		self.assertTrue(self.bpm.init_parser_retq.mock_calls == [unittest.mock.call.delete()])
+
+	def test_set_hammer_breakpoints(self):
+		self.bpm.set_hammer_breakpoints()
+		bps = [self.hdpbp_mock_object, self.hppbp_mock_object, self.pllpbp_mock_object, self.hamrbp_mock_object]
+		for i in range(0, len(bps)):
+			with self.subTest(i=i):
+				self.assertTrue(bps[i].called)
+
+	def test_del_hammer_breakpoints(self):
+		self.bpm.set_hammer_breakpoints()
+		bps = [self.bpm.h_do_parse, self.bpm.h_packrat_parse, self.bpm.perform_lowlevel_parse, self.bpm.h_arena_malloc_raw]
+		self.bpm.del_hammer_breakpoints()
+		for i in range(0, len(bps)):
+			with self.subTest(i=i):
+				self.assertTrue(bps[i].mock_calls == [unittest.mock.call.delete()])
+
+	# TODO
+	#def tearDown(self):
+		#self.bpm.delete
+
+#TODO: tests for PDFMainBreakpoint