pythonpytestfixturespytest-bdd

path does not exist after being created with tmp_path fixture


Edit: here's a git repo for easy testing:

https://gitlab.com/qualisign/ugit-bdd/

I want to refactor some repeated code from a step_def file to a conftest.py file. Here's what the step_def looks like:

@scenario('../features/CLI.feature',
          'store file in object database')

def test_file_stored_by_content_address():
    pass

@given("a file exists at some full path within a ugit dir", target_fixture="file_exists_at_path")
def file_exists_at_path(file_within_ugit_dir):
    return file_within_ugit_dir

@when("I enter ugit hash-object followed by that path")
def file_gets_hashed(file_exists_at_path):
    dir_name = os.path.dirname(file_exists_at_path)
    base_name = os.path.basename(file_exists_at_path)
    os.chdir(dir_name)
    os.system(f'ugit hash-object {base_name}')

@then("this object is stored in a content-addressed location in the subdirectory .ugit/objects")
def object_saved_in_db(file_within_ugit_dir, file_hashed):
    with open(file_hashed, "rb") as f:
        contents = f.read()
        with open(file_path, "rb") as hf:
            assert hf.read() == f.read()

And here's the conftest.py:

import os
import subprocess
import hashlib
import pytest
from pytest_bdd import scenario, given, when, then, parsers

WISE_WORDS = "Don\\'t be a fool!  I\\'ll call you later."

@pytest.fixture(scope="session")
def is_ugit_dir(tmp_path_factory):
    path = tmp_path_factory.mktemp('data')
    os.chdir(path)
    subprocess.run(['ugit', 'init'])
    return path

@pytest.fixture
def file_within_ugit_dir(is_ugit_dir):
    path = is_ugit_dir
    full_path = f'{path}/wise_words.txt'
    os.system(f'echo {WISE_WORDS} > wise_words.txt')
    return full_path


   
 @pytest.fixture
def file_hashed(is_ugit_dir, file_within_ugit_dir):
    """
    Returns the full path to a hash-object within the objects database
    """
    subprocess.run(['ugit', 'hash-object', file_within_ugit_dir])
    # there should now be a file with a sha1 content-address in the following directory
    objects_dir = os.path.dirname(is_ugit_dir)+'/.ugit/objects/'
    with open(file_within_ugit_dir, "rb") as f:
        # first calculate the hash
        sha_hash = hashlib.sha1 (f.read()).hexdigest ()
        return objects_dir+sha_hash

When I run the test, it seems that the temporary directory is not being kept open between steps:

t-74/.ugit/objects/7b5ee3d8d42c66048125a3937a0170ffdaf7b272'

    @then("this object is stored in a content-addressed location in the subdirectory .ugit/objects")
    def object_saved_in_db(file_hashed):
>       with open(file_hashed, "rb") as f:
E       FileNotFoundError: [Errno 2] No such file or directory: '/private/var/folders/m2/99x5jvw95ll6sbtgvj5md9700000gp/T/pytest-of-davidjoseph/pytest-74/.ugit/objects/7b5ee3d8d42c66048125a3937a0170ffdaf7b272'

/Users/davidjoseph/projects/ugit-bdd/tests/step_defs/test_cli.py:43: FileNotFoundError
-------------------------------------- Captured stdout call ---------------------------------------
Initialized empty ugit repository in /private/var/folders/m2/99x5jvw95ll6sbtgvj5md9700000gp/T/pytest-of-davidjoseph/pytest-74/data1/.ugit
7b5ee3d8d42c66048125a3937a0170ffdaf7b272

Is there any way to kee this temp directory open to be reused between fixtures in the conftest.py file, and eventually in the step_def file?


Solution

  • Changing the scope of the is_ugit_dir fixture to "session" as suggested in the comment is sufficient; all the rest are the errors in your own code:

    1. path = tmp_path_factory.mktemp('data')
      os.chdir(path)
      subprocess.run(['ugit', 'init'])
      

      You change the current working directory to /tmp/pytest-smth/data and invoke ugit init in there - I assume the tool creates repository metadata at /tmp/pytest-smth/data/.ugit then. Later, you use

      objects_dir = os.path.dirname(is_ugit_dir)+'/.ugit/objects/'
      

      to create the objects dir - this will get you /tmp/pytest-smth/.ugit/objects. No wonder this directory doesn't exist. Changing it to e.g. objects_dir = is_ugit_dir / '.ugit' / 'objects' fixes the first error. As a follow-up, the return of file_hashed fixture has to be changed to objects_dir / sha_hash to work with pathlib paths.

    2. contents = f.read()
      with open(file_path, "rb") as hf:
          assert hf.read() == f.read()
      

      Aside that file_path is not defined (I guess this should be file_within_ugit_dir), you are reading the file into contents and then again. Why that? Either rewind the file via f.seek(0) before invoking f.read() again or use contents for comparison.

    Here's the full working code, with minimal necessary changes:

    conftest.py

    import os
    import subprocess
    import hashlib
    import pytest
    from pytest_bdd import scenario, given, when, then, parsers
    
    WISE_WORDS = "Don\\'t be a fool!  I\\'ll call you later."
    
    
    @pytest.fixture(scope="session")
    def is_ugit_dir(tmp_path_factory):
        path = tmp_path_factory.mktemp('data')
        os.chdir(path)
        subprocess.run(['ugit', 'init'])
        return path
    
    
    @pytest.fixture
    def file_within_ugit_dir(is_ugit_dir):
        path = is_ugit_dir
        full_path = path / 'wise_words.txt'
        os.system(f'echo {WISE_WORDS} > wise_words.txt')
        return full_path
    
    
    @pytest.fixture
    def file_hashed(is_ugit_dir, file_within_ugit_dir):
        """
        Returns the full path to a hash-object within the objects database
        """
        subprocess.run(['ugit', 'hash-object', file_within_ugit_dir])
        # there should now be a file with a sha1 content-address in the following directory
        objects_dir = is_ugit_dir / '.ugit' / 'objects'
        with open(file_within_ugit_dir, "rb") as f:
            # first calculate the hash
            data = b'blob\x00' + f.read()  # prepend the object type
            sha_hash = hashlib.sha1(data).hexdigest()
            return objects_dir / sha_hash
    

    step_def.py

    import os
    from pytest_bdd import scenario, given, when, then, parsers
    
    
    @scenario('features/CLI.feature', 'store file in object database')
    def test_file_stored_by_content_address():
        pass
    
    
    @given("a file exists at some full path within a ugit dir", target_fixture="file_exists_at_path")
    def file_exists_at_path(file_within_ugit_dir):
        return file_within_ugit_dir
    
    
    @when("I enter ugit hash-object followed by that path")
    def file_gets_hashed(file_exists_at_path):
        dir_name = os.path.dirname(file_exists_at_path)
        base_name = os.path.basename(file_exists_at_path)
        os.chdir(dir_name)
        os.system(f'ugit hash-object {base_name}')
    
    @then("this object is stored in a content-addressed location in the subdirectory .ugit/objects")
    def object_saved_in_db(file_within_ugit_dir, file_hashed):
        with open(file_hashed, "rb") as f:
            contents = f.read().strip(b"blob\x00")
            with open(file_within_ugit_dir, "rb") as hf:
                assert hf.read() == contents