Repository: microsoft/ptvsd

Issue:
Step doesn't stop on next statement
## Environment data

- PTVSD version: 4.2.10b1
- OS and version: Win10
- Python version (& distribution if applicable, e.g. Anaconda): 2.7 or 3.6
- Using VS Code or Visual Studio: VS

Note: This is using the code from PR https://github.com/microsoft/PTVS/pull/5318/files

## Actual behavior

Runs till the end of program

## Expected  behavior

Stop on line 2

## Steps to reproduce:
1. Write a program like this with 2 statements, put breakpoint on first statement

![image](https://user-images.githubusercontent.com/1696845/58133501-d3ea2280-7bd8-11e9-99fe-af9147121fec.png)

2. Start with F5, wait to hit the breakpoint
3. Press F10 to step



Discussion:
[adapterhost.log](https://github.com/microsoft/ptvsd/files/3204742/adapterhost.log)

This requires stop on entry to be on to repro.
Looking at the log, it looks like the first stopped event is for the breakpoint. Then after that is stepped over, the next stopped event has reason=entry (not step!), and VS automatically sent a continue response to it. 

So the problem is that we double-report the stop on first line. In this scenario, it should always be reported as a breakpoint stop (so that VS doesn't auto-continue), but we shouldn't then report a stop-at-entry at all. This is a case similar to when you step to the next line, and it also has a breakpoint; we only report the stop once in that case, and same thing needs to happen here.


Resolving patch:
diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py
--- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py
+++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py
@@ -265,12 +265,12 @@ def make_io_message(self, v, ctx):
     def make_thread_suspend_single_notification(self, py_db, thread_id, stop_reason):
         exc_desc = None
         exc_name = None
+        thread = pydevd_find_thread_by_id(thread_id)
+        info = set_additional_thread_info(thread)
+
         if stop_reason in self._STEP_REASONS:
-            thread = pydevd_find_thread_by_id(thread_id)
-            info = set_additional_thread_info(thread)
             if info.pydev_stop_on_entry:
                 stop_reason = 'entry'
-                info.pydev_stop_on_entry = False
             else:
                 stop_reason = 'step'
         elif stop_reason in self._EXCEPTION_REASONS:
@@ -282,6 +282,9 @@ def make_thread_suspend_single_notification(self, py_db, thread_id, stop_reason)
         else:
             stop_reason = 'pause'
 
+        # At this point we are stopped. This should be false going forward.
+        info.pydev_stop_on_entry = False
+
         if stop_reason == 'exception':
             exception_info_response = build_exception_info_response(
                 py_db, thread_id, -1, set_additional_thread_info, self._iter_visible_frames_info, max_frames=-1)


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -66,12 +66,22 @@ def pytest_report_header(config):
             sys.real_prefix, realpath(sys.real_prefix)))
 
     if hasattr(site, 'getusersitepackages'):
+        paths = site.getusersitepackages()
+        if isinstance(paths, (list, tuple)):
+            real_paths = list(realpath(p) for p in paths)
+        else:
+            real_paths = realpath(paths)
         print('site.getusersitepackages(): %s (%s)' % (
-            site.getusersitepackages(), realpath(site.getusersitepackages())))
+            site.getusersitepackages(), real_paths))
 
     if hasattr(site, 'getsitepackages'):
+        paths = site.getsitepackages()
+        if isinstance(paths, (list, tuple)):
+            real_paths = list(realpath(p) for p in paths)
+        else:
+            real_paths = realpath(paths)
         print('site.getsitepackages(): %s (%s)' % (
-            site.getsitepackages(), realpath(site.getsitepackages())))
+            site.getsitepackages(), real_paths))
 
     for path in sys.path:
         if os.path.exists(path) and os.path.basename(path) == 'site-packages':
diff --git a/tests/func/test_stop_on_entry.py b/tests/func/test_stop_on_entry.py
--- a/tests/func/test_stop_on_entry.py
+++ b/tests/func/test_stop_on_entry.py
@@ -4,19 +4,20 @@
 
 from __future__ import print_function, with_statement, absolute_import
 
+from tests.helpers import get_marked_line_numbers
 from tests.helpers.pattern import Path
 from tests.helpers.session import DebugSession
 import pytest
 
 
 @pytest.mark.parametrize('start_method', ['launch'])
-def test_stop_on_entry(pyfile, run_as, start_method):
+@pytest.mark.parametrize('with_bp', ['with_breakpoint', ''])
+def test_stop_on_entry(pyfile, run_as, start_method, with_bp):
 
     @pyfile
     def code_to_debug():
-        import backchannel
-        from dbgimporter import import_and_enable_debugger
-        import_and_enable_debugger()
+        import backchannel # @bp
+        # import_and_enable_debugger()
         backchannel.write_json('done')
 
     with DebugSession() as session:
@@ -26,16 +27,33 @@ def code_to_debug():
             debug_options=['StopOnEntry'],
             use_backchannel=True,
         )
+        if bool(with_bp):
+            line_numbers = get_marked_line_numbers(code_to_debug)
+            bp_line = line_numbers['bp']
+            session.set_breakpoints(code_to_debug, [bp_line])
+
         session.start_debugging()
 
-        thread_stopped, resp_stacktrace, tid, _ = session.wait_for_thread_stopped(reason='entry')
-        frames = resp_stacktrace.body['stackFrames']
-        assert frames[0]['line'] == 1
-        assert frames[0]['source']['path'] == Path(code_to_debug)
+        if bool(with_bp):
+            thread_stopped, resp_stacktrace, thread_id, _ = session.wait_for_thread_stopped(reason='breakpoint')
+            frames = resp_stacktrace.body['stackFrames']
+            assert frames[0]['line'] == 1
+            assert frames[0]['source']['path'] == Path(code_to_debug)
+
+            session.send_request('next', {'threadId': thread_id}).wait_for_response()
+            thread_stopped, resp_stacktrace, thread_id, _ = session.wait_for_thread_stopped(reason='step')
+            frames = resp_stacktrace.body['stackFrames']
+            assert frames[0]['line'] == 3
+            assert frames[0]['source']['path'] == Path(code_to_debug)
+        else:
+            thread_stopped, resp_stacktrace, tid, _ = session.wait_for_thread_stopped(reason='entry')
+            frames = resp_stacktrace.body['stackFrames']
+            assert frames[0]['line'] == 1
+            assert frames[0]['source']['path'] == Path(code_to_debug)
 
         session.send_request('continue').wait_for_response(freeze=False)
         session.wait_for_termination()
 
         assert session.read_json() == 'done'
 
-        session.wait_for_exit()
\ No newline at end of file
+        session.wait_for_exit()