[tests] Add efficiency tests for scrobbles
This commit is contained in:
196
vrobbler/apps/people/tests/test_person_scrobble_performance.py
Normal file
196
vrobbler/apps/people/tests/test_person_scrobble_performance.py
Normal file
@ -0,0 +1,196 @@
|
||||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from django.db import connection
|
||||
from django.utils import timezone
|
||||
|
||||
from music.aggregators import batch_live_charts, live_charts
|
||||
from scrobbles.models import Scrobble, Track, Artist
|
||||
from videos.models import Video
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_scrobble_queryset_with_related_reduces_queries():
|
||||
"""Test that Scrobble.objects.with_related() reduces query count."""
|
||||
user = User.objects.create_user(username="perfuser", password="testpass123")
|
||||
|
||||
artist = Artist.objects.create(name="Test Artist")
|
||||
track = Track.objects.create(
|
||||
title="Test Track", artist=artist, base_run_time_seconds=180
|
||||
)
|
||||
|
||||
num_scrobbles = 10
|
||||
for i in range(num_scrobbles):
|
||||
Scrobble.objects.create(
|
||||
user=user,
|
||||
track=track,
|
||||
media_type=Scrobble.MediaType.TRACK,
|
||||
played_to_completion=True,
|
||||
timestamp=timezone.now() - timezone.timedelta(days=i),
|
||||
)
|
||||
|
||||
with CaptureQueriesContext(connection) as context:
|
||||
scrobbles = list(Scrobble.objects.with_related().filter(user=user))
|
||||
for s in scrobbles:
|
||||
_ = s.track.title
|
||||
_ = s.track.artist.name
|
||||
|
||||
print(
|
||||
f"\nwith_related() queries for {num_scrobbles} scrobbles: {len(context.captured_queries)}"
|
||||
)
|
||||
for q in context.captured_queries:
|
||||
print(f" {q['sql'][:100]}...")
|
||||
|
||||
assert len(context.captured_queries) < num_scrobbles
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_batch_live_charts_vs_individual_calls():
|
||||
"""Test that batch_live_charts uses fewer queries than individual live_charts calls."""
|
||||
user = User.objects.create_user(username="batchuser", password="testpass123")
|
||||
|
||||
artist = Artist.objects.create(name="Test Artist")
|
||||
track = Track.objects.create(
|
||||
title="Test Track", artist=artist, base_run_time_seconds=180
|
||||
)
|
||||
|
||||
for i in range(5):
|
||||
Scrobble.objects.create(
|
||||
user=user,
|
||||
track=track,
|
||||
media_type=Scrobble.MediaType.TRACK,
|
||||
played_to_completion=True,
|
||||
timestamp=timezone.now() - timezone.timedelta(days=i),
|
||||
)
|
||||
|
||||
with CaptureQueriesContext(connection) as batch_context:
|
||||
batch_result = batch_live_charts(user=user, media_type="Track", limit=15)
|
||||
|
||||
with CaptureQueriesContext(connection) as individual_context:
|
||||
individual_result = {
|
||||
"today": list(
|
||||
live_charts(user=user, media_type="Track", chart_period="today")
|
||||
),
|
||||
"last7": list(
|
||||
live_charts(user=user, media_type="Track", chart_period="last7")
|
||||
),
|
||||
"last30": list(
|
||||
live_charts(user=user, media_type="Track", chart_period="last30")
|
||||
),
|
||||
"year": list(
|
||||
live_charts(user=user, media_type="Track", chart_period="year")
|
||||
),
|
||||
"all": list(live_charts(user=user, media_type="Track", chart_period="all")),
|
||||
}
|
||||
|
||||
print(f"\nbatch_live_charts queries: {len(batch_context.captured_queries)}")
|
||||
print(f"individual live_charts queries: {len(individual_context.captured_queries)}")
|
||||
|
||||
assert len(batch_context.captured_queries) <= len(
|
||||
individual_context.captured_queries
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_as_dict_by_type_uses_with_related():
|
||||
"""Test that as_dict_by_type uses with_related() for reduced queries."""
|
||||
user = User.objects.create_user(username="dictuser", password="testpass123")
|
||||
|
||||
artist = Artist.objects.create(name="Test Artist")
|
||||
track = Track.objects.create(
|
||||
title="Test Track", artist=artist, base_run_time_seconds=180
|
||||
)
|
||||
|
||||
for i in range(5):
|
||||
Scrobble.objects.create(
|
||||
user=user,
|
||||
track=track,
|
||||
media_type=Scrobble.MediaType.TRACK,
|
||||
played_to_completion=True,
|
||||
timestamp=timezone.now() - timezone.timedelta(days=i),
|
||||
)
|
||||
|
||||
scrobbles = Scrobble.objects.filter(user=user)
|
||||
|
||||
with CaptureQueriesContext(connection) as context:
|
||||
result = Scrobble.as_dict_by_type(scrobbles)
|
||||
|
||||
print(f"\nas_dict_by_type queries: {len(context.captured_queries)}")
|
||||
for q in context.captured_queries:
|
||||
print(f" {q['sql'][:100]}...")
|
||||
|
||||
assert len(context.captured_queries) < 10
|
||||
assert "Track" in result
|
||||
assert len(result["Track"]) == 5
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_person_scrobble_junction_efficient_count():
|
||||
"""Test that PersonScrobble junction table enables efficient counts."""
|
||||
from people.models import Person, PersonScrobble
|
||||
|
||||
creator = User.objects.create_user(username="creator2", password="testpass123")
|
||||
other_user = User.objects.create_user(username="other2", password="testpass123")
|
||||
|
||||
person = Person.objects.create(name="Test Person", created_by=creator, user=creator)
|
||||
|
||||
artist = Artist.objects.create(name="Test Artist")
|
||||
track = Track.objects.create(
|
||||
title="Test Track", artist=artist, base_run_time_seconds=180
|
||||
)
|
||||
|
||||
for i in range(10):
|
||||
scrobble = Scrobble.objects.create(
|
||||
user=other_user,
|
||||
track=track,
|
||||
media_type=Scrobble.MediaType.TRACK,
|
||||
played_to_completion=True,
|
||||
log={"with_people_ids": [person.id]},
|
||||
)
|
||||
|
||||
count = person.get_scrobble_count(user=other_user)
|
||||
assert count == 10
|
||||
|
||||
with CaptureQueriesContext(connection) as context:
|
||||
assert person.get_scrobble_count(user=other_user) == 10
|
||||
|
||||
print(f"\nPersonScrobble count queries: {len(context.captured_queries)}")
|
||||
for q in context.captured_queries:
|
||||
print(f" {q['sql'][:100]}...")
|
||||
|
||||
assert len(context.captured_queries) <= 3
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_video_scrobbles_with_related():
|
||||
"""Test prefetching works for video scrobbles."""
|
||||
user = User.objects.create_user(username="videouser", password="testpass123")
|
||||
|
||||
video = Video.objects.create(
|
||||
title="Test Video",
|
||||
video_type=Video.VideoType.MOVIE,
|
||||
base_run_time_seconds=5400,
|
||||
)
|
||||
|
||||
for i in range(3):
|
||||
Scrobble.objects.create(
|
||||
user=user,
|
||||
video=video,
|
||||
media_type=Scrobble.MediaType.VIDEO,
|
||||
played_to_completion=True,
|
||||
timestamp=timezone.now() - timezone.timedelta(days=i),
|
||||
)
|
||||
|
||||
with CaptureQueriesContext(connection) as context:
|
||||
scrobbles = list(Scrobble.objects.with_related().filter(user=user))
|
||||
for s in scrobbles:
|
||||
_ = s.video.title
|
||||
_ = s.video.video_type
|
||||
|
||||
print(f"\nVideo scrobbles with_related() queries: {len(context.captured_queries)}")
|
||||
for q in context.captured_queries:
|
||||
print(f" {q['sql'][:100]}...")
|
||||
|
||||
assert len(context.captured_queries) < 5
|
||||
Reference in New Issue
Block a user