Repository: microsoft/ptvsd

Issue:
TypeError: on_breakpoints_changed() got an unexpected keyword argument 'remove'
Happens whenever an exception breakpoint is added or removed with the current bits. Full stack:
```
  File "/home/int19h/.local/lib/python3.7/site-packages/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py", line 47, in process_net_command
    cmd = on_command(py_db, cmd_id, seq, text)
  File "/home/int19h/.local/lib/python3.7/site-packages/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py", line 461, in cmd_remove_exception_break
    self.api.remove_python_exception_breakpoint(py_db, exception)
  File "/home/int19h/.local/lib/python3.7/site-packages/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py", line 424, in remove_python_exception_breakpoint
    py_db.on_breakpoints_changed(remove=True)
TypeError: on_breakpoints_changed() got an unexpected keyword argument 'remove'
```
Unclear why, since `on_breakpoints_changed` does have that argument declared. 

Happens on both Python 2 and Python 3. 


Discussion:
Will create a test and provide a fix shortly (this happened because the parameter was `removed` and not `remove`).

Resolving patch:
diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py
--- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py
+++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py
@@ -421,8 +421,8 @@ def remove_python_exception_breakpoint(self, py_db, exception):
         except:
             pydev_log.debug("Error while removing exception %s" % sys.exc_info()[0])
 
-        py_db.on_breakpoints_changed(remove=True)
-    
+        py_db.on_breakpoints_changed(removed=True)
+
     def remove_plugins_exception_breakpoint(self, py_db, exception_type, exception):
         # I.e.: no need to initialize lazy (if we didn't have it in the first place, we can't remove
         # anything from it anyways).
@@ -437,6 +437,5 @@ def remove_plugins_exception_breakpoint(self, py_db, exception_type, exception):
         else:
             raise NameError(exception_type)
 
-        py_db.on_breakpoints_changed(remove=True)    
-    
+        py_db.on_breakpoints_changed(removed=True)
 


Test patch:
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py b/src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py
@@ -6,7 +6,7 @@
 
 from tests_python import debugger_unittest
 from tests_python.debugger_unittest import get_free_port, overrides, IS_CPYTHON, IS_JYTHON, IS_IRONPYTHON, \
-    IS_PY3K
+    IS_PY3K, CMD_ADD_DJANGO_EXCEPTION_BREAK, CMD_REMOVE_DJANGO_EXCEPTION_BREAK
 
 import sys
 
@@ -187,6 +187,12 @@ def write_add_breakpoint_django(self, line, func, template):
         self.log.append('write_add_django_breakpoint: %s line: %s func: %s' % (breakpoint_id, line, func))
         return breakpoint_id
 
+    def write_add_exception_breakpoint_django(self, exception='Exception'):
+        self.write('%s\t%s\t%s' % (CMD_ADD_DJANGO_EXCEPTION_BREAK, self.next_seq(), exception))
+
+    def write_remove_exception_breakpoint_django(self, exception='Exception'):
+        self.write('%s\t%s\t%s' % (CMD_REMOVE_DJANGO_EXCEPTION_BREAK, self.next_seq(), exception))
+
     def create_request_thread(self, uri):
         outer = self
 
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py b/src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py
@@ -955,6 +955,9 @@ def write_add_exception_breakpoint_with_policy(
             exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries])))
         self.log.append('write_add_exception_breakpoint: %s' % (exception,))
 
+    def write_remove_exception_breakpoint(self, exception):
+        self.write('%s\t%s\t%s' % (CMD_REMOVE_EXCEPTION_BREAK, self.next_seq(), exception))
+
     def write_remove_breakpoint(self, breakpoint_id):
         self.write("%s\t%s\t%s\t%s\t%s" % (
             CMD_REMOVE_BREAK, self.next_seq(), 'python-line', self.get_main_filename(), breakpoint_id))
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
@@ -945,6 +945,8 @@ def test_case_django_a(case_setup_django):
 def test_case_django_b(case_setup_django):
     with case_setup_django.test_file(EXPECTED_RETURNCODE='any') as writer:
         writer.write_add_breakpoint_django(4, None, 'name.html')
+        writer.write_add_exception_breakpoint_django()
+        writer.write_remove_exception_breakpoint_django()
         writer.write_make_initial_run()
 
         t = writer.create_request_thread('my_app/name')
@@ -2378,8 +2380,9 @@ def do_kill(writer):
         writer.finished_ok = True
 
 
+@pytest.mark.parametrize('handle', [True, False])
 @pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
-def test_remote_unhandled_exceptions(case_setup_remote):
+def test_remote_unhandled_exceptions(case_setup_remote, handle):
 
     def check_test_suceeded_msg(writer, stdout, stderr):
         return 'TEST SUCEEDED' in ''.join(stderr)
@@ -2400,14 +2403,21 @@ def additional_output_checks(writer, stdout, stderr):
         writer.log.append('waiting for breakpoint hit')
         hit = writer.wait_for_breakpoint_hit()
 
+        # Add, remove and add back
         writer.write_add_exception_breakpoint_with_policy('Exception', '0', '1', '0')
+        writer.write_remove_exception_breakpoint('Exception')
+        writer.write_add_exception_breakpoint_with_policy('Exception', '0', '1', '0')
+
+        if not handle:
+            writer.write_remove_exception_breakpoint('Exception')
 
         writer.log.append('run thread')
         writer.write_run_thread(hit.thread_id)
 
-        writer.log.append('waiting for uncaught exception')
-        hit = writer.wait_for_breakpoint_hit(REASON_UNCAUGHT_EXCEPTION)
-        writer.write_run_thread(hit.thread_id)
+        if handle:
+            writer.log.append('waiting for uncaught exception')
+            hit = writer.wait_for_breakpoint_hit(REASON_UNCAUGHT_EXCEPTION)
+            writer.write_run_thread(hit.thread_id)
 
         writer.log.append('finished ok')
         writer.finished_ok = True