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

Adds test class for checking query durations #3380

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
11 changes: 11 additions & 0 deletions bookwyrm/tests/models/test_bookwyrm_export_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@

from unittest.mock import patch

from django.db import connection
from django.utils import timezone
from django.test import TestCase

from bookwyrm import models
from bookwyrm.tests.query_logger import QueryLogger, raise_long_query_runtime
from bookwyrm.utils.tar import BookwyrmTarFile


# pylint: disable=invalid-name
class BookwyrmExportJob(TestCase):
"""testing user export functions"""

Expand Down Expand Up @@ -159,6 +162,14 @@ def setUp(self):
models.bookwyrm_export_job.create_export_json_task(job_id=self.job.id)
self.job.refresh_from_db()

def test_create_export_job_query_time(self):
"""test the creation of the job"""
query_logger = QueryLogger()
with connection.execute_wrapper(query_logger):
with patch("bookwyrm.models.bookwyrm_export_job.create_archive_task.delay"):
models.bookwyrm_export_job.create_export_json_task(job_id=self.job.id)
raise_long_query_runtime(query_logger.queries)

def test_add_book_to_user_export_job(self):
"""does AddBookToUserExportJob ...add the book to the export?"""
self.assertIsNotNone(self.job.export_json["books"])
Expand Down
44 changes: 44 additions & 0 deletions bookwyrm/tests/query_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
""" Log query runtimes for testing """
import time
from environs import Env

env = Env()
env.read_env()
MAX_QUERY_DURATION = float(env("MAX_QUERY_DURATION"))


class QueryLogger:
"""Returns the sql and duration for any query run
Taken wholesale from:
https://docs.djangoproject.com/en/dev/topics/db/instrumentation/
"""

def __init__(self):
self.queries = []

# pylint: disable=too-many-arguments
def __call__(self, execute, sql, params, many, context):
current_query = {"sql": sql, "params": params, "many": many}
start = time.monotonic()
try:
result = execute(sql, params, many, context)
except Exception as err: # pylint: disable=broad-except
current_query["status"] = "error"
current_query["exception"] = err
raise
else:
current_query["status"] = "ok"
return result
finally:
duration = time.monotonic() - start
current_query["duration"] = duration
self.queries.append(current_query)


def raise_long_query_runtime(queries, threshold=MAX_QUERY_DURATION):
"""Raises an exception if any query took longer than the threshold"""
for query in queries:
if query["duration"] > threshold:
raise Exception( # pylint: disable=broad-exception-raised
"This looks like a slow query:", query["duration"], query["sql"]
)
7 changes: 6 additions & 1 deletion bookwyrm/tests/views/admin/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
from unittest.mock import patch

from django.contrib.auth.models import Group
from django.db import connection
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory

from bookwyrm import models, views
from bookwyrm.management.commands import initdb
from bookwyrm.tests.query_logger import QueryLogger, raise_long_query_runtime
from bookwyrm.tests.validate_html import validate_html


Expand Down Expand Up @@ -46,7 +48,10 @@ def test_dashboard(self):
request = self.factory.get("")
request.user = self.local_user

result = view(request)
query_logger = QueryLogger()
with connection.execute_wrapper(query_logger):
result = view(request)
raise_long_query_runtime(query_logger.queries)
self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())
self.assertEqual(result.status_code, 200)
44 changes: 31 additions & 13 deletions bookwyrm/tests/views/books/test_book.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db import connection
from django.http import Http404
from django.template.response import TemplateResponse
from django.test import TestCase
Expand All @@ -15,9 +16,10 @@

from bookwyrm import forms, models, views
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.tests.query_logger import QueryLogger, raise_long_query_runtime
from bookwyrm.tests.validate_html import validate_html


# pylint: disable=invalid-name
class BookViews(TestCase):
"""books books books"""

Expand Down Expand Up @@ -51,6 +53,8 @@
remote_id="https://example.com/book/1",
parent_work=cls.work,
)
for i in range(10000):
models.Edition.objects.create(title=i, parent_work=cls.work)

models.SiteSettings.objects.create()

Expand All @@ -68,9 +72,15 @@
)
request = self.factory.get("")
request.user = self.local_user
with patch("bookwyrm.views.books.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.book.id)

query_logger = QueryLogger()
with connection.execute_wrapper(query_logger):
with patch("bookwyrm.views.books.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.book.id)

raise_long_query_runtime(query_logger.queries)

Check failure on line 82 in bookwyrm/tests/views/books/test_book.py

View workflow job for this annotation

GitHub Actions / Tests (pytest)

BookViews.test_book_page Exception: ('This looks like a slow query:', 0.013602515000002313, 'SELECT "bookwyrm_book"."id", "bookwyrm_book"."created_date", "bookwyrm_book"."updated_date", "bookwyrm_book"."remote_id", "bookwyrm_book"."origin_id", "bookwyrm_book"."openlibrary_key", "bookwyrm_book"."inventaire_id", "bookwyrm_book"."librarything_key", "bookwyrm_book"."goodreads_key", "bookwyrm_book"."bnf_id", "bookwyrm_book"."viaf", "bookwyrm_book"."wikidata", "bookwyrm_book"."asin", "bookwyrm_book"."aasin", "bookwyrm_book"."isfdb", "bookwyrm_book"."search_vector", "bookwyrm_book"."last_edited_by_id", "bookwyrm_book"."connector_id", "bookwyrm_book"."title", "bookwyrm_book"."sort_title", "bookwyrm_book"."subtitle", "bookwyrm_book"."description", "bookwyrm_book"."languages", "bookwyrm_book"."series", "bookwyrm_book"."series_number", "bookwyrm_book"."subjects", "bookwyrm_book"."subject_places", "bookwyrm_book"."cover", "bookwyrm_book"."preview_image", "bookwyrm_book"."first_published_date", "bookwyrm_book"."published_date", "bookwyrm_book"."first_published_date_precision", "bookwyrm_book"."published_date_precision", "bookwyrm_edition"."book_ptr_id", "bookwyrm_edition"."isbn_10", "bookwyrm_edition"."isbn_13", "bookwyrm_edition"."oclc_number", "bookwyrm_edition"."pages", "bookwyrm_edition"."physical_format", "bookwyrm_edition"."physical_format_detail", "bookwyrm_edition"."publishers", "bookwyrm_edition"."parent_work_id", "bookwyrm_edition"."edition_rank", T4."id", T4."created_date", T4."updated_date", T4."remote_id", T4."origin_id", T4."openlibrary_key", T4."inventaire_id", T4."librarything_key", T4."goodreads_key", T4."bnf_id", T4."viaf", T4."wikidata", T4."asin", T4."aasin", T4."isfdb", T4."search_vector", T4."last_edited_by_id", T4."connector_id", T4."title", T4."sort_title", T4."subtitle", T4."description", T4."languages", T4."series", T4."series_number", T4."subjects", T4."subject_places", T4."cover", T4."preview_image", T4."first_published_date", T4."published_date", T4."first_published_date_precision", T4."published_date_precision", "bookwyrm_work"."book_ptr_id", "bookwyrm_work"."lccn" FROM "bookwyrm_edition" INNER JOIN "bookwyrm_book" ON ("bookwyrm_edition"."book_ptr_id" = "bookwyrm_book"."id") LEFT OUTER JOIN "bookwyrm_work" ON ("bookwyrm_edition"."parent_work_id" = "bookwyrm_work"."book_ptr_id") LEFT OUTER JOIN "bookwyrm_book" T4 ON ("bookwyrm_work"."book_ptr_id" = T4."id") LEFT OUTER JOIN "bookwyrm_mergedbook" ON ("bookwyrm_book"."id" = "bookwyrm_mergedbook"."merged_into_id") WHERE ("bookwyrm_edition"."book_ptr_id" = %s OR "bookwyrm_edition"."parent_work_id" = %s OR "bookwyrm_mergedbook"."deleted_id" = %s) ORDER BY "bookwyrm_edition"."edition_rank" DESC LIMIT 1')

self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())

Expand Down Expand Up @@ -109,26 +119,34 @@

request = self.factory.get("")
request.user = self.local_user
with patch("bookwyrm.views.books.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.book.id, user_statuses="review")
query_logger = QueryLogger()
with connection.execute_wrapper(query_logger):
with patch("bookwyrm.views.books.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.book.id, user_statuses="review")
raise_long_query_runtime(query_logger.queries)

Check failure on line 127 in bookwyrm/tests/views/books/test_book.py

View workflow job for this annotation

GitHub Actions / Tests (pytest)

BookViews.test_book_page_statuses Exception: ('This looks like a slow query:', 0.013183548000029077, 'SELECT "bookwyrm_book"."id", "bookwyrm_book"."created_date", "bookwyrm_book"."updated_date", "bookwyrm_book"."remote_id", "bookwyrm_book"."origin_id", "bookwyrm_book"."openlibrary_key", "bookwyrm_book"."inventaire_id", "bookwyrm_book"."librarything_key", "bookwyrm_book"."goodreads_key", "bookwyrm_book"."bnf_id", "bookwyrm_book"."viaf", "bookwyrm_book"."wikidata", "bookwyrm_book"."asin", "bookwyrm_book"."aasin", "bookwyrm_book"."isfdb", "bookwyrm_book"."search_vector", "bookwyrm_book"."last_edited_by_id", "bookwyrm_book"."connector_id", "bookwyrm_book"."title", "bookwyrm_book"."sort_title", "bookwyrm_book"."subtitle", "bookwyrm_book"."description", "bookwyrm_book"."languages", "bookwyrm_book"."series", "bookwyrm_book"."series_number", "bookwyrm_book"."subjects", "bookwyrm_book"."subject_places", "bookwyrm_book"."cover", "bookwyrm_book"."preview_image", "bookwyrm_book"."first_published_date", "bookwyrm_book"."published_date", "bookwyrm_book"."first_published_date_precision", "bookwyrm_book"."published_date_precision", "bookwyrm_edition"."book_ptr_id", "bookwyrm_edition"."isbn_10", "bookwyrm_edition"."isbn_13", "bookwyrm_edition"."oclc_number", "bookwyrm_edition"."pages", "bookwyrm_edition"."physical_format", "bookwyrm_edition"."physical_format_detail", "bookwyrm_edition"."publishers", "bookwyrm_edition"."parent_work_id", "bookwyrm_edition"."edition_rank", T4."id", T4."created_date", T4."updated_date", T4."remote_id", T4."origin_id", T4."openlibrary_key", T4."inventaire_id", T4."librarything_key", T4."goodreads_key", T4."bnf_id", T4."viaf", T4."wikidata", T4."asin", T4."aasin", T4."isfdb", T4."search_vector", T4."last_edited_by_id", T4."connector_id", T4."title", T4."sort_title", T4."subtitle", T4."description", T4."languages", T4."series", T4."series_number", T4."subjects", T4."subject_places", T4."cover", T4."preview_image", T4."first_published_date", T4."published_date", T4."first_published_date_precision", T4."published_date_precision", "bookwyrm_work"."book_ptr_id", "bookwyrm_work"."lccn" FROM "bookwyrm_edition" INNER JOIN "bookwyrm_book" ON ("bookwyrm_edition"."book_ptr_id" = "bookwyrm_book"."id") LEFT OUTER JOIN "bookwyrm_work" ON ("bookwyrm_edition"."parent_work_id" = "bookwyrm_work"."book_ptr_id") LEFT OUTER JOIN "bookwyrm_book" T4 ON ("bookwyrm_work"."book_ptr_id" = T4."id") LEFT OUTER JOIN "bookwyrm_mergedbook" ON ("bookwyrm_book"."id" = "bookwyrm_mergedbook"."merged_into_id") WHERE ("bookwyrm_edition"."book_ptr_id" = %s OR "bookwyrm_edition"."parent_work_id" = %s OR "bookwyrm_mergedbook"."deleted_id" = %s) ORDER BY "bookwyrm_edition"."edition_rank" DESC LIMIT 1')

self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())

self.assertEqual(result.status_code, 200)
self.assertEqual(result.context_data["statuses"].object_list[0], review)

with patch("bookwyrm.views.books.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.book.id, user_statuses="comment")
with connection.execute_wrapper(query_logger):
with patch("bookwyrm.views.books.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.book.id, user_statuses="comment")
raise_long_query_runtime(query_logger.queries)
self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())
self.assertEqual(result.status_code, 200)
self.assertEqual(result.context_data["statuses"].object_list[0], comment)

with patch("bookwyrm.views.books.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.book.id, user_statuses="quotation")
with connection.execute_wrapper(query_logger):
with patch("bookwyrm.views.books.books.is_api_request") as is_api:
is_api.return_value = False
result = view(request, self.book.id, user_statuses="quotation")
raise_long_query_runtime(query_logger.queries)
self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())
self.assertEqual(result.status_code, 200)
Expand Down
25 changes: 17 additions & 8 deletions bookwyrm/tests/views/landing/test_landing.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
""" test for app action functionality """
from unittest.mock import patch
from django.contrib.auth.models import AnonymousUser
from django.db import connection
from django.http import Http404
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory

from bookwyrm import models
from bookwyrm import views
from bookwyrm.tests.query_logger import QueryLogger, raise_long_query_runtime
from bookwyrm.tests.validate_html import validate_html


# pylint: disable=invalid-name
class LandingViews(TestCase):
"""pages you land on without really trying"""

Expand Down Expand Up @@ -39,7 +42,7 @@ def setUp(self):

@patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions")
def test_home_page(self, _):
"""there are so many views, this just makes sure it LOADS"""
"""home page for a logged in user"""
view = views.Home.as_view()
request = self.factory.get("")
request.user = self.local_user
Expand All @@ -54,6 +57,19 @@ def test_home_page(self, _):
self.assertEqual(result.status_code, 200)
validate_html(result.render())

def test_landing(self):
"""tests landing page for an anonymous user"""
view = views.Landing.as_view()
request = self.factory.get("")

query_logger = QueryLogger()
with connection.execute_wrapper(query_logger):
result = view(request)
raise_long_query_runtime(query_logger.queries)

validate_html(result.render())
self.assertIsInstance(result, TemplateResponse)

def test_about_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.about
Expand Down Expand Up @@ -105,10 +121,3 @@ def test_impressum_page_on(self):
self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())
self.assertEqual(result.status_code, 200)

def test_landing(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.Landing.as_view()
request = self.factory.get("")
result = view(request)
self.assertIsInstance(result, TemplateResponse)
Loading
Loading