Pytest - tmpdir_factory in pytest_generate_tests - python

So I have two main portion of code:
Generates an extensive collection of config files in a directory.
Runs a single config file generated by the previous.
I want to run a test, where first I execute code1 and generate all files and than for each config file run code2 and verify that the results if good.
My attempt so far was:
#pytest.fixture(scope='session')
def pytest_generate_tests(metafunc, tmpdir_factory):
path = os.path.join(tmpdir_factory, "configs")
gc.main(path,
gc.VARIANTS, gc.MODELS,
default_curvature_avg=0.0,
curvature_avg_variation=0.9,
default_gradient_avg=0.0,
gradient_avg_variation=0.9,
default_inversion="approximate",
vary_inversion=False,
vary_projections=True)
params = []
for model in os.listdir(path):
model_path = os.path.join(path, model)
for dataset in os.listdir(model_path):
dataset_path = os.path.join(model_path, dataset)
for file_name in os.listdir(dataset_path):
config_file = os.path.join(dataset_path, file_name)
folder = os.path.join(dataset_path, file_name[:-5])
tmpdir_factory.mktemp(folder)
params.append(dict(config_file=config_file, output_folder=folder))
metafunc.addcall(funcargs=dict(config_file=config_file, output_folder=folder))
def test_compile_and_error(config_file, output_folder):
final_error = main(config_file, output_folder)
assert final_error < 0.9
However, the tmpdir_factory fixture does not work for the pytest_generate_tests method. My questions is how to achieve my goal by generating all of the tests?

First and most importantly,
pytest_generate_tests is meant to be a hook in pytest and not a name for a fixture function. Get rid of the #pytest.fixture before it and have another look in its docs. Hooks should be written in the conftest.py file or a plugin file, and are collected automatically according to the pytest_ prefix.
Now for your matter:
Just use a temporary directory manually using:
import tempfile
import shutil
dirpath = tempfile.mkdtemp()
inside pytest_generate_tests. Save dirpath in a global in the conftest,
and delete in pytest_sessionfinish using
# ... do stuff with dirpath
shutil.rmtree(dirpath)
Source: https://stackoverflow.com/a/3223615/3858507
Remember that if you have more than one test case, pytest_generate_tests will be called for each one. So you better save all your tempdirs in some list and delete all of them in the end. In contrast, if you only need one tempdir than think about using the hook pytest_sesssionstart to create it there and use it later.

Related

Directory not found in unittest

When calling unit test in another directory, it does not import the data I needed.
Directory goes like this:
GenerateValues
--app folder
----process_data.py
----input_files.py
----model_data folder
------data.json
----block_tests
------test_1.py
------test_2.py
--test.py
I need to call unittest in test.py.
In the unit tests under block_tests, I have been calling process_data for assertion. process_data.py calls data.json and use it to do the processing.
input_files.py
folder = "model_data"
data1 = f"{folder}/data.json"
process_data.py
class ProcessData:
def __init__(self):
self.load_data()
def load_data(self):
fn = model.data1
#loads data then to be used in process
test_1.py
class TestData(unittest.TestCase):
#classmethod
def setUpClass(cls) -> None:
cls.data_service = ProcessData()
test.py
loader = unittest.TestLoader()
test_runner = unittest.runner.TextTestRunner()
tests = loader.discover("block_tests")
test_runner.run(tests)
Error is this:
FileNotFoundError: [Errno 2] No such file or directory: 'model_data/data.json'
Pathlib provides some great helpers that will make this a bit easier for you. If you change input_files.py to be:
from pathlib import Path
folder = Path(__file__).parent / "model_data"
data1 = folder / "data.json"
This uses the __file__ attribute to get the path of input_files.py, then uses pathlib.Path to figure out the directory it lives in(.parent). This can then be used to point to the model_data folder, and then the specific json you need to load.
Caveat: I may have the exact paths wrong here because I am reading your directory tree rather than looking at a real one. You may need to tweak the exact path wrangling!

Can't find the file from views.py

I am stuck with a really weird problem.
I have a file called: "test.txt".
It is in the same directory with views.py. But I can't read it... FileNotFoundError.
But if I create read_file.py in the same directory with views.py and test.txt, it works absolutely fine.
What is wrong with views? Is this some sort of restriction by Django?
This code works on read_file, doesn't work on views.py:
fkey = open("test.txt", "rb")
key = fkey.read()
I think the problem may be relative vs absolute file handling. Using the following Python snippet, you can work out where the interpreter's current working directory is:
import os
print(os.getcwd())
You should notice it's not in the same directory as the views.py, as views.py is likely being invoked/called from another module. You may choose to change all your open calls to include the whole path to these files, or use an implementation like this:
import os
# This function can be used as a replacement for `open`
# which will allow you to access files from the file.
def open_relative(path, flag="r"):
# This builds the relative path by joining the current directory
# plus the current filename being executed.
relative_path = os.path.join(os.path.dirname(__file__), path)
return open(relative_path, flag) # return file handler
Then, this should work:
fkey = open_relative("test.txt", "rb")
key = fkey.read()
Hope this helps!

Relative paths in unittest python to run tests individually and as a whole

There are already several answered questions to this topic but actually not addressing my problem.
I'm using PyCharm 2021.2 and I want to be able to run unit tests both, individually and as a whole. This is important because if I have many tests and only some of them fail, I usually debug my code and would like to run only my failed tests to see if my debugging was successful. Only then do I want to re-run all the tests.
My setup is as follows:
So, for instance, I want to:
Right-click the folder tests and run all tests in this folder (in my example screenshot, there is only one test_MyClass.py but usually here would be many such tests).
Right-click an individual test, e.g. test_MyClass.py, and run it on its own.
Both possibilities usually work fine. However, when my single tests use some relative paths, for instance, to read some test assets (in my case from the folder containing_folder/tests/testassets), only the option 1) works. The option 2) runs into a FileNotFoundError: No such file or directory.
The code to reproduce this behavior is:
MyClass.py:
class MyClass:
_content = None
def set_content(self, content):
self._content = content
def get_content(self):
return self._content
test_MyClass.py:
import unittest
import io
from ..MyClass import MyClass
class MyClassTests(unittest.TestCase):
myClassInstance = None
#classmethod
def setUpClass(cls):
cls.myClassInstance = MyClass()
def get_file(self, use_case):
path_to_file = "testassets/" + use_case + ".txt"
with io.open(path_to_file, 'r', encoding="utf-8") as file:
file = file.read()
return file
def test_uc_file1(self):
file_content = self.get_file("uc_1")
self.myClassInstance.set_content(file_content)
self.assertEquals("test1", self.myClassInstance.get_content())
def test_uc_file2(self):
file_content = self.get_file("uc_2")
self.myClassInstance.set_content(file_content)
self.assertEquals("test2", self.myClassInstance.get_content())
It seems that path_to_file = "testassets/" + use_case + ".txt" only works as a relative path in the 1) option, but not in the 2) option.
How can I recognize programmatically, which option 1) or 2) I'm starting a test in PyCharm? And which path would then I have to choose for option 2)? I tried ../testassets, ../../testassets, ../../, , ../ but none of them worked for option 2).
Ok, I found how to accomplish what I want.
First of all, I got rid of relative paths when importing. Instead of from ..MyClass import MyClass I use simply from MyClass import MyClass.
Second, my methods setUpClass and get_file now look like this:
#classmethod
def setUpClass(cls):
cls.path = os.path.normpath(os.path.abspath(__file__)) # requires import os
if os.path.isfile(cls.path):
cls.path = os.path.dirname(cls.path)
cls.myClassInstance = MyClass()
def get_file(self, use_case):
path_to_file = self.path + "/testassets/" + use_case + ".txt"
with io.open(path_to_file, 'r', encoding="utf-8") as file:
file = file.read()
return file
The point is that os.path.abspath(__file__) returns a root path of either the directory containing_folder/tests if I choose option 1) to start all tests or the filename containing_folder/tests/test_MyClass.py if I choose option 2) to start a single test. In the if statement
if os.path.isfile(cls.path):
cls.path = os.path.dirname(cls.path)
I generalize both special cases to get the root directory of all the tests and easily find the test assets relative to them.

Recursively create directories prior to opening file for writing

I need to write to a file (truncating) and the path it is on itself might not exist). For example, I want to write to /tmp/a/b/c/config, but /tmp/a itself might not exist. Then, open('/tmp/a/b/c/config', 'w') would not work, obviously, since it doesn't make the necessary directories. However, I can work with the following code:
import os
config_value = 'Foo=Bar' # Temporary placeholder
config_dir = '/tmp/a/b/c' # Temporary placeholder
config_file_path = os.path.join(config_dir, 'config')
if not os.path.exists(config_dir):
os.makedirs(config_dir)
with open(config_file_path, 'w') as f:
f.write(config_value)
Is there a more Pythonic way to do this? Both Python 2.x and Python 3.x would be nice to know (even though I use 2.x in my code, due to dependency reasons).
If you're repeating this pattern in multiple places, you could create your own Context Manager that extends open() and overloads __enter__():
import os
class OpenCreateDirs(open):
def __enter__(self, filename, *args, **kwargs):
file_dir = os.path.dirname(filename)
if not os.path.exists(file_dir):
os.makedirs(file_dir)
super(OpenCreateDirs, self).__enter__(filename, *args, **kwargs)
Then your code becomes:
import os
config_value = 'Foo=Bar' # Temporary placeholder
config_file_path = os.path.join('/tmp/a/b/c', 'config')
with OpenCreateDirs(config_file_path, 'w') as f:
f.write(config_value)
The first method to be called when you run with open(...) as f: is open.__enter__(). So by creating directories before calling super(...).__enter__(), you create the directories before attempting to open the file.

Why is my attempt at monkey patching shutil not working?

So I am attempting to monkeypatch the shutil module in order to use a recent fix to their make_archive function that allows the creation fo large zip files.
I am proof of concepting something so figured a quick hack to get this issue out of the way would allow me to get on with what I want to do.
My code:
import shutil
import os
def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
zip_filename = base_name + ".zip"
archive_dir = os.path.dirname(base_name)
if not os.path.exists(archive_dir):
if logger is not None:
logger.info("creating %s", archive_dir)
if not dry_run:
os.makedirs(archive_dir)
# If zipfile module is not available, try spawning an external 'zip'
# command.
try:
import zipfile
except ImportError:
zipfile = None
if zipfile is None:
shutil._call_external_zip(base_dir, zip_filename, verbose, dry_run)
else:
if logger is not None:
logger.info("creating '%s' and adding '%s' to it",
zip_filename, base_dir)
if not dry_run:
zip = zipfile.ZipFile(zip_filename, "w",
compression=zipfile.ZIP_DEFLATED,
allowZip64=True) # this is the extra argument
for dirpath, dirnames, filenames in os.walk(base_dir):
for name in filenames:
path = os.path.normpath(os.path.join(dirpath, name))
if os.path.isfile(path):
zip.write(path, path)
if logger is not None:
logger.info("adding '%s'", path)
zip.close()
shutil._make_zipfile = _make_zipfile
# This function calls _make_zipfile when it runs
shutil.make_archive('blah', someargs)
So the issue is... it doesn't do anything. I am clearly doing something stupid, but for the life of me I can not see what it is. I am assuming there is something obvious that I have become blind to after looking at it for so long, so need some fresh eyes. I have tried following methods/checking against answers described in these:
Monkey-patch Python class
Python monkey patch private function
and What is a monkey patch?
plus some others. No joy
You'll have to update the _ARCHIVE_FORMATS mapping; it stores a reference to the function on import, so before you can patch it. shutil.make_archive() uses that mapping, and not the _make_zipfile function directly.
You can use the public shutil.register_archive_format() function to re-define the zip archiver:
shutil.register_archive_format('zip', _make_zipfile, description='ZIP file')
This replaces the existing callable registered for the zip format.

Categories