I need to explicitly delete a fixture after it is used. I know that pytest-django by default drops all objects on teardown but in this particular case I need to do it manually. However, although my tests are marked as pytest.mark.django_db, I am able to create a fixture, but unable to delete it after a yield line:
import pytest
from tgapps.models import TelegramApp
#pytest.fixture(scope='module')
def some_fixture():
app = TelegramApp.objects.create(
session_data=b'\xa2\x8f#',
app_owner_phone=79856235474,
app_id=182475,
app_hash='aad9ab4384fea1af0342b77b606d13b0'
)
yield app
print('deleting object...')
app.delete()
class TestTelegramServiceObject(object):
#pytest.mark.django_db
def test1(self, some_fixture):
print('Fixture created:')
print(some_fixture)
this is my test output:
============================= test session starts ==============================
platform darwin -- Python 3.6.4, pytest-3.4.0, py-1.5.2, pluggy-0.6.0
Django settings: inviter.settings.staging (from ini file)
rootdir: /Users/1111/_projects/fasttrack/inviter, inifile: pytest.ini
plugins: mock-1.7.1, dotenv-0.1.0, django-3.1.2
collected 1 item
test_example.py E.Fixture created:
<79856235474 - 182475>
deleting object...
tests/api/test_example.py:25 (TestTelegramServiceObject.test1)
#pytest.fixture(scope='module')
def some_fixture():
app = TelegramApp.objects.create(
session_data=b'\xa2\x8f#',
app_owner_phone=79856235474,
app_id=182475,
app_hash='aad9ab4384fea1af0342b77b606d13b0'
)
yield app
print('deleting object...')
> app.delete()
test_example.py:21:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Users/1111/.virtualenvs/inviter-WB5rPISo/lib/python3.6/site-packages/django/db/models/base.py:890: in delete
collector.collect([self], keep_parents=keep_parents)
/Users/1111/.virtualenvs/inviter-WB5rPISo/lib/python3.6/site-packages/django/db/models/deletion.py:221: in collect
elif sub_objs:
/Users/1111/.virtualenvs/inviter-WB5rPISo/lib/python3.6/site-packages/django/db/models/query.py:276: in __bool__
self._fetch_all()
/Users/1111/.virtualenvs/inviter-WB5rPISo/lib/python3.6/site-packages/django/db/models/query.py:1179: in _fetch_all
self._result_cache = list(self._iterable_class(self))
/Users/1111/.virtualenvs/inviter-WB5rPISo/lib/python3.6/site-packages/django/db/models/query.py:53: in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
/Users/1111/.virtualenvs/inviter-WB5rPISo/lib/python3.6/site-packages/django/db/models/sql/compiler.py:1062: in execute_sql
cursor = self.connection.cursor()
/Users/1111/.virtualenvs/inviter-WB5rPISo/lib/python3.6/site-packages/django/db/backends/base/base.py:255: in cursor
return self._cursor()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <django.db.backends.postgresql.base.DatabaseWrapper object at 0x1048bf080>
name = None
def _cursor(self, name=None):
> self.ensure_connection()
E Failed: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.
Why is this? and how do I enable DB assess on fixture teardown?
The db fixture as mentioned by Andreas Profous is function scoped so that wouldn't work here.
What you need to do is:
#pytest.fixture(scope='module')
def some_fixture(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
app = TelegramApp.objects.create(
session_data=b'\xa2\x8f#',
app_owner_phone=79856235474,
app_id=182475,
app_hash='aad9ab4384fea1af0342b77b606d13b0'
)
yield app
with django_db_blocker.unblock():
print('deleting object...')
app.delete()
django_db_setup ensures the test database is setup (and ready) for additional records to be added. django_db_blocker is what the db fixture uses to allow function scoped modifications. The reason why it's function scoped is so that it will behave how TestCase methods behave in the default unittest framework where the records are rolled back after each test. This is a good thing, it ensures a modification of a record in one test won't change the outcome in other test.
So be very careful when creating fixtures that aren't function scoped as your modifications won't by default be wrapped in a transaction and rolled back. Using the django_db_blocker gives you hooks into the django_db_blocker.unblock() method which removes the block from modifying the database. This is required in your non-function scoped fixtures.
You need to require the db fixture in some_fixture:
def some_fixture(db):
...
Related
I have been trying to run a process when testing my browser app. The process is one of my fixture for pytest testing, and it should be running when i start my test. However, it showed error when i run it. I am quite confused because it should be a simple process. this is my code:
#pytest.fixture(scope='class')
def chrome_driver(request):
""" Selenium webdriver with options to support running in GitHub actions
Note:
For CI: `headless` and `disable-gpu` not commented out
For running on your computer: `headless` and `disable-gpu` to be commented out
"""
options = ChromeOptions()
#options.add_argument("--headless") # use for GitHub Actions CI
#options.add_argument('--disable-gpu') # use for GitHub Actions CI
options.add_argument("--window-size=1920,1080")
chrome_driver = Chrome(options=options)
request.cls.driver = chrome_driver
yield
chrome_driver.close()
#pytest.fixture(scope='class')
def run_app(app):
"""
Fixture to run the Flask app for Selenium browser tests
"""
multiprocessing.set_start_method("spawn") # Needed in Python 3.8 and later
#process = multiprocessing.Process(target=app.run, args=())
#process.start()
process = multiprocessing.Process(target=app.run, args=())
process.start()
process.join()
yield process
process.terminate()
above are the two fixtures for my testing, and they will be run the same time when i start my test. Below is the errors:
test setup failed
app = <Flask 'flask_app'>
#pytest.fixture(scope='class')
def run_app(app):
"""
Fixture to run the Flask app for Selenium browser tests
"""
multiprocessing.set_start_method("spawn") # Needed in Python 3.8 and later
#process = multiprocessing.Process(target=app.run, args=())
#process.start()
process = multiprocessing.Process(target=app.run, args=())
> process.start()
..\conftest.py:138:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\..\..\AppData\Local\Programs\Python\Python310\lib\multiprocessing\process.py:121: in start
self._popen = self._Popen(self)
..\..\..\..\AppData\Local\Programs\Python\Python310\lib\multiprocessing\context.py:224: in _Popen
return _default_context.get_context().Process._Popen(process_obj)
..\..\..\..\AppData\Local\Programs\Python\Python310\lib\multiprocessing\context.py:327: in _Popen
return Popen(process_obj)
..\..\..\..\AppData\Local\Programs\Python\Python310\lib\multiprocessing\popen_spawn_win32.py:93: in __init__
reduction.dump(process_obj, to_child)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
obj = <Process name='Process-1' parent=26100 initial>
file = <_io.BufferedWriter name=11>, protocol = None
def dump(obj, file, protocol=None):
'''Replacement for pickle.dump() using ForkingPickler.'''
> ForkingPickler(file, protocol).dump(obj)
E AttributeError: Can't pickle local object 'Flask.__init__.<locals>.<lambda>'
I was able to get my similar test to run if I instead set multiprocessing.set_start_method("fork") and running the single test inside the IDE. As soon as I run it in a test suite I get the 'pickle' error though.
As Henry pointed out, forking will avoid the pickle error. Set in a session scoped fixture to executed once in pytest session.
#pytest.fixture(scope='session')
def init_multiprocessing():
multiprocessing.set_start_method("fork")
#pytest.fixture
def run_app(app, init_multiprocessing):
process = multiprocessing.Process(target=app.run)
process.start()
yield process
process.terminate()
process.join()
I would like to request an S3 image and then serve it using Phoenix.
def getImage(conn, %{"id" => uuid}) do
file = ExAws.S3.get_object("bucket", "images/image.jpg")
|> ExAws.request
conn
|> put_resp_content_type("image/jpg")
|> put_resp_header(
"content-disposition",
"attachment; filename=\"file.jpg\""
)
|> send_resp(200, file)
end
I have found endless tutorials on how to upload to S3 but nothing on retrieving. Thank you in advance!
You have to pattern match against get_object function and extract image content from that.
def getImage(conn, %{"id" => uuid}) do
{:ok, %{body: image_content}} = ExAws.S3.get_object("bucket", "images/image.jpg")
|> ExAws.request
conn
|> put_resp_content_type("image/jpg")
|> put_resp_header(
"content-disposition",
"attachment; filename=\"file.jpg\""
)
|> send_resp(200, image_content)
end
I was trying to check that all links work on a Yesod website home page. I wrote this hSpec test.
module Handler.HomeSpec (spec) where
import Data.Either (fromRight)
import qualified Data.Text as T
import Network.Wai.Test (simpleBody)
import TestImport
import Yesod.Test.TransversingCSS (findAttributeBySelector)
getAllLinks :: YesodExample site [Text]
getAllLinks = withResponse $ \res -> do
let links = fromRight [] findAttributeBySelector (simpleBody res) "a" "href"
return $ T.concat <$> links
spec :: Spec
spec = withApp $
describe "Homepage" $ do
it "checks all links" $ do
get HomeR
statusIs 200
links <- getAllLinks
forM_ links $ \oneLink -> do
get HomeR
statusIs 200
get oneLink
statusIs 200
Everything compiles ok but the get function gets rid of the host part of the URLs you feed it. For example, when you give it https://github.com/zigazou/bazasso, it will try to fetch /zigazou/bazasso which returns a 404 code.
Is there a way to make it work like I want ?
Should I add a function that removes external links from the tests ?
Is it simply not the right place to do it ?
The simpler, the better: I've removed everything that starts with a protocol from the links that will be checked. Thanks to #ncaq for your comments.
module Handler.HomeSpec (spec) where
import Data.Either (fromRight)
import qualified Data.Text as T
import Network.Wai.Test (simpleBody)
import TestImport
import Yesod.Test.TransversingCSS (findAttributeBySelector)
isRelative :: Text -> Bool
isRelative url
| T.take 7 url == "http://" = False
| T.take 8 url == "https://" = False
| T.take 7 url == "mailto:" = False
| T.take 4 url == "tel:" = False
| otherwise = True
getAllLinks :: YesodExample site [Text]
getAllLinks = withResponse $ \res -> do
let currentHtml = simpleBody res
links = fromRight [] $ findAttributeBySelector currentHtml "a" "href"
return $ filter isRelative $ T.concat <$> links
spec :: Spec
spec = withApp $
describe "Homepage" $ do
it "checks all links" $ do
get HomeR
statusIs 200
links <- getAllLinks
forM_ links $ \oneLink -> do
get HomeR
statusIs 200
get oneLink
statusIs 200
So, I am trying to implement a way to add database migrations without an ORM/ODM for my SQLite3 database, and the error I'm getting (syntax error: expecting token 'EOF', not 'end') is for this code:
src/project/database/migration/migrations/1.cr:
require "../migration"
module Project
ver = 1
migration = Migration.new ver
migration.register_up |db| do
db.exec "create table version (version int)"
db.exec "insert into version values (?)", ver
end
migration.register_down |db| do
db.exec "drop table version"
end
Migrations[ver] = migration
end
I can't see any immediate issues with the syntax. This file imports the following file because it needs the class and the line Migrations = [] of Migration:
src/project/database/migration/migration.cr:
require "db"
require "sqlite3"
module Project
Migrations = [] of Migration
class Migration
def initialize(#version : Int)
end
# Registers a callback that will be called when the `up`-method is called.
# The callback must return either `true` for a successful migration,
# or `false` for a failed migration. If an `up` migration has
# failed, the `down` migration will be called to restore the database
# back to its previous state.
# The callback will receive an instance of `DB::Database`
#
# Example:
#
# ```
# migration = Migration.new(1)
#
# migration.register_up |db| do
# # Advance migration
# end
#
# migration.register_down |db| do
# # Recede migration
# end
# ```
def register_up(&block : (DB::Database) -> Bool)
#up = block
end
# Registers a callback that will be called when the `down`-method is called.
# See the `register_up` method for more information
def register_down(&block : (DB::Database) -> Bool)
#down = block
end
# Advances DB to the next version
def up(conn : DB::Database)
result = #up.call(conn)
unless result
# Failed migration, rollback
#down.call(conn)
raise Exception.new(`Failed to migrate database to version: #{#version}. Rolling back.`)
end
end
# Recedes DB to the previous version
def down(conn : DB::Database)
result = #down.call(conn)
unless result
# Failed migration, rollback
#up.call(conn)
raise Exception.new(`Failed to migrate database to version: #{#version - 1}. Rolling back.`)
end
end
end
end
Any ideas?
Syntax mistake is here:
migration.register_up |db| do
# ...
end
Should be:
migration.register_up do |db|
# ...
end
And the same in register_down.
See "Yield arguments" section in Blocks and Procs.
I'm trying to use OUnit with OCaml.
The unit code source (unit.ml) is as follows:
open OUnit
let empty_list = []
let list_a = [1;2;3]
let test_list_length _ =
assert_equal 1 (List.length empty_list);
assert_equal 3 (List.length list_a)
(* etc, etc *)
let test_list_append _ =
let list_b = List.append empty_list [1;2;3] in
assert_equal list_b list_a
let suite = "OUnit Example" >::: ["test_list_length" >:: test_list_length;
"test_list_append" >:: test_list_append]
let _ =
run_test_tt_main suite
With ocamlc unit.ml, I got an error message Error: Unbound module OUnit. Why is this? How can I use the OUnit in OCaml. I installed OUnit with opam.
You should use for example:
ocamlfind ocamlc -package oUnit -linkpkg -o unit unit.ml
ocamlfind is an utility that encapsulates lookup of installed libraries. You can also avoid composing compiler-invocation yourself and use a build tool.