path does not exist after being created with tmp_path fixture

1.9k views Asked by At

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?

2

There are 2 answers

0
hoefling On BEST ANSWER

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
1
alv2017 On

I would say that you have logic issue in your code. According to the test scenario the fixture file_hashed must return a path to the existing file containing hash. One can see it here:

@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()

In the conftest.py you are not creating the file containing hash. You are creating a dummy link instead, and because there is nothing on that link, you get FileNotFoundError. Error is here (you code does not create a hash file):

@pytest.fixture
def file_hashed(is_ugit_dir, file_within_ugit_dir):
    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