Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session created while cassette is active is still attached to cassette when context manager exits #109

Closed
colonelpanic8 opened this issue Sep 18, 2014 · 3 comments

Comments

@colonelpanic8
Copy link
Collaborator

I think that this might have to do with requests using a connection pool which would keep a reference to the any VCRConnection instances.

Here is a test that illustrates the issue:

def test_session_can_make_requests_after_requests_unpatched(tmpdir):
    with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
        session = requests.session()
        session.get('http://httpbin.org/get')

    with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
        session = requests.session()
        session.get('http://httpbin.org/get')

    session.get('http://httpbin.org/status/200')

#108 does not solve this and in fact has another similar issue which is that sessions created in one context do not pick up context from any new context blocks that are created (or at least their behavior is inconsistent in that respect). Here is a failing test for that issue in case you are interested:

def test_nested_context_managers_with_session_created_before_first_nesting(scheme, tmpdir):
    '''
    This tests ensures that a session that was created while one cassette was
    '''
    url = scheme + '://httpbin.org/bytes/1024'
    with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
        session = requests.session()
        first_body = session.get(url).content
        with vcr.use_cassette(str(tmpdir.join('second_nested.yaml'))):
            second_body = session.get(url).content
            third_body = requests.get(url).content

    with vcr.use_cassette(str(tmpdir.join('second_nested.yaml'))):
        session = requests.session()
        assert session.get(url).content == second_body
        with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
            assert session.get(url).content == first_body
        assert session.get(url).content == third_body
@colonelpanic8
Copy link
Collaborator Author

traceback:

tmpdir = local('/private/var/folders/61/1x6fdz8j3xvgv5z8m0k22r8h0000gn/T/pytest-138/test_session_can_make_requests0')

def test_session_can_make_requests_after_requests_unpatched(tmpdir):
    with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
        session = requests.session()
        session.get('http://httpbin.org/get')

    with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
        session = requests.session()
        session.get('http://httpbin.org/get')
  session.get('http://httpbin.org/status/200')

tests/integration/test_requests.py:164:


.tox/py34requests22/lib/python3.4/site-packages/requests/sessions.py:395: in get
return self.request('GET', url, *_kwargs)
.tox/py34requests22/lib/python3.4/site-packages/requests/sessions.py:383: in request
resp = self.send(prep, *_send_kwargs)
.tox/py34requests22/lib/python3.4/site-packages/requests/sessions.py:486: in send
r = adapter.send(request, **kwargs)
.tox/py34requests22/lib/python3.4/site-packages/requests/adapters.py:330: in send
timeout=timeout
.tox/py34requests22/lib/python3.4/site-packages/requests/packages/urllib3/connectionpool.py:480: in urlopen
body=body, headers=headers)
.tox/py34requests22/lib/python3.4/site-packages/requests/packages/urllib3/connectionpool.py:315: in _make_request
httplib_response = conn.getresponse()


self = <vcr.patch.VCRRequestsHTTPConnection/private/var/folders/61/1x6fdz8j3xvgv5z8m0k22r8h0000gn/T/pytest-138/test_session_can_make_requests0/test_session_after_unpatched.yaml object at 0x10bc8c550>, _ = False

def getresponse(self, _=False):
    '''Retrieve a the response'''
    # Check to see if the cassette has a response for this request. If so,
    # then return it
    if self.cassette.can_play_response_for(self._vcr_request):
        log.info(
            "Playing response for {0} from cassette".format(
                self._vcr_request
            )
        )
        response = self.cassette.play_response(self._vcr_request)
        return VCRHTTPResponse(response)
    else:
        if self.cassette.write_protected and self.cassette._filter_request(self._vcr_request):
            raise CannotOverwriteExistingCassetteException(
                "Can't overwrite existing cassette (%r) in "
                "your current record mode (%r)."
              % (self.cassette._path, self.cassette.record_mode)
            )

E vcr.errors.CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('/private/var/folders/61/1x6fdz8j3xvgv5z8m0k22r8h0000gn/T/pytest-138/test_session_can_make_requests0/test_session_after_unpatched.yaml') in your current record mode ('once').

@kevin1024
Copy link
Owner

Hmm, is session holding a copy of the connection that needs to be unpatched?

@colonelpanic8 colonelpanic8 changed the title Session created while cassette is still attached to cassette when context manager Session created while cassette is active is still attached to cassette when context manager exits Sep 19, 2014
@colonelpanic8
Copy link
Collaborator Author

As I mentioned, it has a connection pool instance which can hold reference to an instance of the connection class, so even though the class has been reset in the module, the instance is already of the patched type. This also happens in the other direction. i.e.

def test_session_created_before_use_cassette_is_patched(tmpdir, scheme):
    url = scheme + '://httpbin.org/bytes/1024'
    # Record arbitrary, random data to the cassette
    with vcr.use_cassette(str(tmpdir.join('session_created_outside.yaml'))):
        session = requests.session()
        body = session.get(url).content

    # Create a session outside of any cassette context manager
    session = requests.session()
    # Make a request to make sure that a connectionpool is instantiated
    session.get(scheme + '://httpbin.org/get')

    with vcr.use_cassette(str(tmpdir.join('session_created_outside.yaml'))):
        # These should only be the same if the patching succeeded.
        assert session.get(url).content == body

fails.

I think this is actually a somewhat concerning issue -- If you were to make a fixture that uses its own use cassette and makes some requests on a session that is then accessible in the calling test, the session would still be attached to the fixtures cassette, and it would be unusable in the test context. As a simple example (edit: 2bf23b2 fixes this (but not the other two tests but in a less than desirable way :/)

@pytest.fixture
@use_cassette('login.yaml')
 def user(self):
     return User(session)

@use_cassette('user_test.yaml')
def test_something(user):
    user.do_stuff()

This test would behave strangely (if we assume that the User object does some type of initialization with the session), because the connection pool could still have some connection objects from the first fixture.

colonelpanic8 added a commit to colonelpanic8/vcrpy that referenced this issue Sep 20, 2014
colonelpanic8 added a commit to colonelpanic8/vcrpy that referenced this issue Sep 20, 2014
colonelpanic8 added a commit to colonelpanic8/vcrpy that referenced this issue Sep 21, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants