From 229752da87fda0118eb66e266808f0965247828f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20C=2E=20Leit=C3=A3o?= Date: Tue, 20 May 2014 07:35:29 +0200 Subject: [PATCH] Reorganized, renamed, and improved docstrings of TestCases. --- ...test_xapian_backend.py => test_backend.py} | 230 +++--------------- ...{test_live_xapian.py => test_interface.py} | 14 +- .../{test_xapian_query.py => test_query.py} | 215 +++++++++++++++- 3 files changed, 246 insertions(+), 213 deletions(-) rename tests/xapian_tests/tests/{test_xapian_backend.py => test_backend.py} (76%) rename tests/xapian_tests/tests/{test_live_xapian.py => test_interface.py} (95%) rename tests/xapian_tests/tests/{test_xapian_query.py => test_query.py} (58%) diff --git a/tests/xapian_tests/tests/test_xapian_backend.py b/tests/xapian_tests/tests/test_backend.py similarity index 76% rename from tests/xapian_tests/tests/test_xapian_backend.py rename to tests/xapian_tests/tests/test_backend.py index 134166d..7ed4551 100644 --- a/tests/xapian_tests/tests/test_xapian_backend.py +++ b/tests/xapian_tests/tests/test_backend.py @@ -6,18 +6,15 @@ import xapian import subprocess import os -from django.conf import settings from django.db import models from django.test import TestCase -from haystack import connections, reset_search_queries +from haystack import connections from haystack import indexes from haystack.backends.xapian_backend import InvalidIndexError, _term_to_xapian_value -from haystack.models import SearchResult -from haystack.query import SearchQuerySet, SQ from haystack.utils.loading import UnifiedIndex -from core.models import MockTag, MockModel, AnotherMockModel, AFourthMockModel +from core.models import MockTag, MockModel, AnotherMockModel from core.tests.mocks import MockSearchResult @@ -158,8 +155,8 @@ class XapianSimpleMockIndex(indexes.SearchIndex): class HaystackBackendTestCase(object): """ - An abstract TestCase that implements a hack to ensure connections - has the mock index + Abstract TestCase that implements an hack to ensure `connections` + has the right index It has a method get_index() that returns a SearchIndex that must be overwritten. @@ -183,13 +180,19 @@ class HaystackBackendTestCase(object): connections['default']._index = self.old_ui -class XapianBackendTestCase(HaystackBackendTestCase, TestCase): +class BackendIndexationTestCase(HaystackBackendTestCase, TestCase): + """ + Tests indexation behavior. + + Tests related to how the backend indexes terms, + values, and others go here. + """ def get_index(self): return XapianSimpleMockIndex() def setUp(self): - super(XapianBackendTestCase, self).setUp() + super(BackendIndexationTestCase, self).setUp() mock = XapianMockModel() mock.id = 1 self.backend.update(self.index, [mock]) @@ -298,13 +301,19 @@ class XapianBackendTestCase(HaystackBackendTestCase, TestCase): self.assertIn('corrup\xe7\xe3o', terms) -class XapianSearchBackendTestCase(HaystackBackendTestCase, TestCase): +class BackendFeaturesTestCase(HaystackBackendTestCase, TestCase): + """ + Tests supported features on the backend side. + + Tests to features implemented on the backend + go here. + """ def get_index(self): return XapianMockSearchIndex() def setUp(self): - super(XapianSearchBackendTestCase, self).setUp() + super(BackendFeaturesTestCase, self).setUp() self.sample_objs = [] @@ -589,10 +598,16 @@ class XapianSearchBackendTestCase(HaystackBackendTestCase, TestCase): if xapian.minor_version() >= 2: self.assertEqual(str(self.backend.parse_query('name:da*')), - 'Xapian::Query((XNAMEdavid1:(pos=1) SYNONYM XNAMEdavid2:(pos=1) SYNONYM XNAMEdavid3:(pos=1)))') + 'Xapian::Query((' + 'XNAMEdavid1:(pos=1) SYNONYM ' + 'XNAMEdavid2:(pos=1) SYNONYM ' + 'XNAMEdavid3:(pos=1)))') else: self.assertEqual(str(self.backend.parse_query('name:da*')), - 'Xapian::Query((XNAMEdavid1:(pos=1) OR XNAMEdavid2:(pos=1) OR XNAMEdavid3:(pos=1)))') + 'Xapian::Query((' + 'XNAMEdavid1:(pos=1) OR ' + 'XNAMEdavid2:(pos=1) OR ' + 'XNAMEdavid3:(pos=1)))') self.assertEqual(str(self.backend.parse_query('name:david1..david2')), 'Xapian::Query(VALUE_RANGE 7 david1 david2)') @@ -648,192 +663,3 @@ class XapianSearchBackendTestCase(HaystackBackendTestCase, TestCase): self.backend.silently_fail = False self.assertRaises(InvalidIndexError, self.backend.more_like_this, mock) - - -class LiveXapianMockSearchIndex(indexes.SearchIndex): - text = indexes.CharField(document=True, use_template=True) - name = indexes.CharField(model_attr='author', faceted=True) - pub_date = indexes.DateTimeField(model_attr='pub_date') - title = indexes.CharField() - - def get_model(self): - return MockModel - - -class LiveXapianSearchQueryTestCase(HaystackBackendTestCase, TestCase): - """ - SearchQuery specific tests - """ - fixtures = ['initial_data.json'] - - def get_index(self): - return LiveXapianMockSearchIndex() - - def setUp(self): - super(LiveXapianSearchQueryTestCase, self).setUp() - - self.backend.update(self.index, MockModel.objects.all()) - - self.sq = connections['default'].get_query() - - def test_get_spelling(self): - self.sq.add_filter(SQ(content='indxd')) - self.assertEqual(self.sq.get_spelling_suggestion(), 'indexed') - self.assertEqual(self.sq.get_spelling_suggestion('indxd'), 'indexed') - - def test_startswith(self): - self.sq.add_filter(SQ(name__startswith='da')) - self.assertEqual([result.pk for result in self.sq.get_results()], [1, 2, 3]) - - def test_build_query_gt(self): - self.sq.add_filter(SQ(name__gt='m')) - self.assertEqual(str(self.sq.build_query()), - 'Xapian::Query(( AND_NOT VALUE_RANGE 3 a m))') - - def test_build_query_gte(self): - self.sq.add_filter(SQ(name__gte='m')) - self.assertEqual(str(self.sq.build_query()), - 'Xapian::Query(VALUE_RANGE 3 m zzzzzzzzzzzzzzzzzzzzzzzzzzzz' - 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' - 'zzzzzzzzzzzzzz)') - - def test_build_query_lt(self): - self.sq.add_filter(SQ(name__lt='m')) - self.assertEqual(str(self.sq.build_query()), - 'Xapian::Query(( AND_NOT ' - 'VALUE_RANGE 3 m zzzzzzzzzzzzzzzzzzzzzz' - 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' - 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz))') - - def test_build_query_lte(self): - self.sq.add_filter(SQ(name__lte='m')) - self.assertEqual(str(self.sq.build_query()), 'Xapian::Query(VALUE_RANGE 3 a m)') - - def test_build_query_multiple_filter_types(self): - self.sq.add_filter(SQ(content='why')) - self.sq.add_filter(SQ(pub_date__lte=datetime.datetime(2009, 2, 10, 1, 59, 0))) - self.sq.add_filter(SQ(name__gt='david')) - self.sq.add_filter(SQ(title__gte='B')) - self.sq.add_filter(SQ(django_id__in=[1, 2, 3])) - self.assertEqual(str(self.sq.build_query()), - 'Xapian::Query(((Zwhi OR why) AND ' - 'VALUE_RANGE 5 00010101000000 20090210015900 AND ' - '( AND_NOT VALUE_RANGE 3 a david) AND ' - 'VALUE_RANGE 7 b zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' - 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz AND ' - '(QQ000000000001 OR QQ000000000002 OR QQ000000000003)))') - - def test_log_query(self): - reset_search_queries() - self.assertEqual(len(connections['default'].queries), 0) - - # Stow. - old_debug = settings.DEBUG - settings.DEBUG = False - - len(self.sq.get_results()) - self.assertEqual(len(connections['default'].queries), 0) - - settings.DEBUG = True - # Redefine it to clear out the cached results. - self.sq = connections['default'].get_query() - self.sq.add_filter(SQ(name='bar')) - len(self.sq.get_results()) - self.assertEqual(len(connections['default'].queries), 1) - self.assertEqual(str(connections['default'].queries[0]['query_string']), 'Xapian::Query((ZXNAMEbar OR XNAMEbar))') - - # And again, for good measure. - self.sq = connections['default'].get_query() - self.sq.add_filter(SQ(name='bar')) - self.sq.add_filter(SQ(text='moof')) - len(self.sq.get_results()) - self.assertEqual(len(connections['default'].queries), 2) - self.assertEqual(str(connections['default'].queries[0]['query_string']), 'Xapian::Query((ZXNAMEbar OR XNAMEbar))') - self.assertEqual(str(connections['default'].queries[1]['query_string']), 'Xapian::Query(((ZXNAMEbar OR XNAMEbar) AND (ZXTEXTmoof OR XTEXTmoof)))') - - # Restore. - settings.DEBUG = old_debug - - -class LiveXapianSearchQuerySetTestCase(HaystackBackendTestCase, TestCase): - """ - SearchQuerySet specific tests - """ - fixtures = ['initial_data.json'] - - def get_index(self): - return LiveXapianMockSearchIndex() - - def setUp(self): - super(LiveXapianSearchQuerySetTestCase, self).setUp() - - self.backend.update(self.index, MockModel.objects.all()) - self.sq = connections['default'].get_query() - self.sqs = SearchQuerySet() - - def test_result_class(self): - # Assert that we're defaulting to ``SearchResult``. - sqs = self.sqs.all() - self.assertTrue(isinstance(sqs[0], SearchResult)) - - # Custom class. - sqs = self.sqs.result_class(MockSearchResult).all() - self.assertTrue(isinstance(sqs[0], MockSearchResult)) - - # Reset to default. - sqs = self.sqs.result_class(None).all() - self.assertTrue(isinstance(sqs[0], SearchResult)) - - def test_facet(self): - self.assertEqual(len(self.sqs.facet('name').facet_counts()['fields']['name']), 3) - - -class XapianBoostMockSearchIndex(indexes.SearchIndex): - text = indexes.CharField( - document=True, use_template=True, - template_name='search/indexes/core/mockmodel_template.txt' - ) - author = indexes.CharField(model_attr='author', weight=2.0) - editor = indexes.CharField(model_attr='editor') - pub_date = indexes.DateField(model_attr='pub_date') - - def get_model(self): - return AFourthMockModel - - -class XapianBoostBackendTestCase(HaystackBackendTestCase, TestCase): - - def get_index(self): - return XapianBoostMockSearchIndex() - - def setUp(self): - super(XapianBoostBackendTestCase, self).setUp() - - self.sample_objs = [] - for i in range(1, 5): - mock = AFourthMockModel() - mock.id = i - if i % 2: - mock.author = 'daniel' - mock.editor = 'david' - else: - mock.author = 'david' - mock.editor = 'daniel' - mock.pub_date = datetime.date(2009, 2, 25) - datetime.timedelta(days=i) - self.sample_objs.append(mock) - - self.backend.update(self.index, self.sample_objs) - - def test_boost(self): - sqs = SearchQuerySet() - - self.assertEqual(len(sqs.all()), 4) - - results = sqs.filter(SQ(author='daniel') | SQ(editor='daniel')) - - self.assertEqual([result.id for result in results], [ - 'core.afourthmockmodel.1', - 'core.afourthmockmodel.3', - 'core.afourthmockmodel.2', - 'core.afourthmockmodel.4' - ]) diff --git a/tests/xapian_tests/tests/test_live_xapian.py b/tests/xapian_tests/tests/test_interface.py similarity index 95% rename from tests/xapian_tests/tests/test_live_xapian.py rename to tests/xapian_tests/tests/test_interface.py index d1d057b..824bcfd 100644 --- a/tests/xapian_tests/tests/test_live_xapian.py +++ b/tests/xapian_tests/tests/test_interface.py @@ -9,16 +9,19 @@ from haystack.query import SearchQuerySet from xapian_tests.models import Document from xapian_tests.search_indexes import DocumentIndex -from xapian_tests.tests.test_xapian_backend import get_terms +from xapian_tests.tests.test_backend import pks -def pks(results): - return [result.pk for result in results] +class InterfaceTestCase(TestCase): + """ + Tests the interface of Xapian-Haystack. - -class LiveXapianTestCase(TestCase): + Tests related to usability and expected behavior + go here. + """ def setUp(self): + super(InterfaceTestCase, self).setUp() types_names = ['book', 'magazine', 'article'] texts = ['This is a huge text', @@ -53,6 +56,7 @@ class LiveXapianTestCase(TestCase): def tearDown(self): Document.objects.all().delete() self.backend.clear() + super(InterfaceTestCase, self).tearDown() def test_count(self): self.assertEqual(self.queryset.count(), Document.objects.count()) diff --git a/tests/xapian_tests/tests/test_xapian_query.py b/tests/xapian_tests/tests/test_query.py similarity index 58% rename from tests/xapian_tests/tests/test_xapian_query.py rename to tests/xapian_tests/tests/test_query.py index 21b1a86..e49fe1b 100644 --- a/tests/xapian_tests/tests/test_xapian_query.py +++ b/tests/xapian_tests/tests/test_query.py @@ -2,17 +2,20 @@ from __future__ import unicode_literals import datetime +from django.conf import settings from django.test import TestCase from haystack import indexes -from haystack import connections -from haystack.query import SQ +from haystack import connections, reset_search_queries +from haystack.models import SearchResult +from haystack.query import SearchQuerySet, SQ -from core.models import MockModel, AnotherMockModel -from xapian_tests.tests.test_xapian_backend import HaystackBackendTestCase +from core.models import MockModel, AnotherMockModel, AFourthMockModel +from core.tests.mocks import MockSearchResult +from xapian_tests.tests.test_backend import HaystackBackendTestCase -class XapianMockQueryIndex(indexes.SearchIndex): +class MockQueryIndex(indexes.SearchIndex): text = indexes.CharField(document=True) pub_date = indexes.DateTimeField() title = indexes.CharField() @@ -24,7 +27,7 @@ class XapianMockQueryIndex(indexes.SearchIndex): class XapianSearchQueryTestCase(HaystackBackendTestCase, TestCase): def get_index(self): - return XapianMockQueryIndex() + return MockQueryIndex() def setUp(self): super(XapianSearchQueryTestCase, self).setUp() @@ -231,3 +234,203 @@ class XapianSearchQueryTestCase(HaystackBackendTestCase, TestCase): self.assertEqual(str(self.sq.build_query()), 'Xapian::Query(((Zwhi OR why) AND ' '("XTITLE1" OR "XTITLE2" OR "XTITLE3")))') + + +class MockSearchIndex(indexes.SearchIndex): + text = indexes.CharField(document=True, use_template=True) + name = indexes.CharField(model_attr='author', faceted=True) + pub_date = indexes.DateTimeField(model_attr='pub_date') + title = indexes.CharField() + + def get_model(self): + return MockModel + + +class SearchQueryTestCase(HaystackBackendTestCase, TestCase): + """ + Tests expected behavior of + SearchQuery. + """ + fixtures = ['initial_data.json'] + + def get_index(self): + return MockSearchIndex() + + def setUp(self): + super(SearchQueryTestCase, self).setUp() + + self.backend.update(self.index, MockModel.objects.all()) + + self.sq = connections['default'].get_query() + + def test_get_spelling(self): + self.sq.add_filter(SQ(content='indxd')) + self.assertEqual(self.sq.get_spelling_suggestion(), 'indexed') + self.assertEqual(self.sq.get_spelling_suggestion('indxd'), 'indexed') + + def test_startswith(self): + self.sq.add_filter(SQ(name__startswith='da')) + self.assertEqual([result.pk for result in self.sq.get_results()], [1, 2, 3]) + + def test_build_query_gt(self): + self.sq.add_filter(SQ(name__gt='m')) + self.assertEqual(str(self.sq.build_query()), + 'Xapian::Query(( AND_NOT VALUE_RANGE 3 a m))') + + def test_build_query_gte(self): + self.sq.add_filter(SQ(name__gte='m')) + self.assertEqual(str(self.sq.build_query()), + 'Xapian::Query(VALUE_RANGE 3 m zzzzzzzzzzzzzzzzzzzzzzzzzzzz' + 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' + 'zzzzzzzzzzzzzz)') + + def test_build_query_lt(self): + self.sq.add_filter(SQ(name__lt='m')) + self.assertEqual(str(self.sq.build_query()), + 'Xapian::Query(( AND_NOT ' + 'VALUE_RANGE 3 m zzzzzzzzzzzzzzzzzzzzzz' + 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' + 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz))') + + def test_build_query_lte(self): + self.sq.add_filter(SQ(name__lte='m')) + self.assertEqual(str(self.sq.build_query()), 'Xapian::Query(VALUE_RANGE 3 a m)') + + def test_build_query_multiple_filter_types(self): + self.sq.add_filter(SQ(content='why')) + self.sq.add_filter(SQ(pub_date__lte=datetime.datetime(2009, 2, 10, 1, 59, 0))) + self.sq.add_filter(SQ(name__gt='david')) + self.sq.add_filter(SQ(title__gte='B')) + self.sq.add_filter(SQ(django_id__in=[1, 2, 3])) + self.assertEqual(str(self.sq.build_query()), + 'Xapian::Query(((Zwhi OR why) AND ' + 'VALUE_RANGE 5 00010101000000 20090210015900 AND ' + '( AND_NOT VALUE_RANGE 3 a david) AND ' + 'VALUE_RANGE 7 b zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' + 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz AND ' + '(QQ000000000001 OR QQ000000000002 OR QQ000000000003)))') + + def test_log_query(self): + reset_search_queries() + self.assertEqual(len(connections['default'].queries), 0) + + # Stow. + old_debug = settings.DEBUG + settings.DEBUG = False + + len(self.sq.get_results()) + self.assertEqual(len(connections['default'].queries), 0) + + settings.DEBUG = True + # Redefine it to clear out the cached results. + self.sq = connections['default'].get_query() + self.sq.add_filter(SQ(name='bar')) + len(self.sq.get_results()) + self.assertEqual(len(connections['default'].queries), 1) + self.assertEqual(str(connections['default'].queries[0]['query_string']), + 'Xapian::Query((ZXNAMEbar OR XNAMEbar))') + + # And again, for good measure. + self.sq = connections['default'].get_query() + self.sq.add_filter(SQ(name='bar')) + self.sq.add_filter(SQ(text='moof')) + len(self.sq.get_results()) + self.assertEqual(len(connections['default'].queries), 2) + self.assertEqual(str(connections['default'].queries[0]['query_string']), + 'Xapian::Query((' + 'ZXNAMEbar OR ' + 'XNAMEbar))') + self.assertEqual(str(connections['default'].queries[1]['query_string']), + 'Xapian::Query((' + '(ZXNAMEbar OR XNAMEbar) AND ' + '(ZXTEXTmoof OR XTEXTmoof)))') + + # Restore. + settings.DEBUG = old_debug + + +class LiveSearchQuerySetTestCase(HaystackBackendTestCase, TestCase): + """ + SearchQuerySet specific tests + """ + fixtures = ['initial_data.json'] + + def get_index(self): + return MockSearchIndex() + + def setUp(self): + super(LiveSearchQuerySetTestCase, self).setUp() + + self.backend.update(self.index, MockModel.objects.all()) + self.sq = connections['default'].get_query() + self.sqs = SearchQuerySet() + + def test_result_class(self): + # Assert that we're defaulting to ``SearchResult``. + sqs = self.sqs.all() + self.assertTrue(isinstance(sqs[0], SearchResult)) + + # Custom class. + sqs = self.sqs.result_class(MockSearchResult).all() + self.assertTrue(isinstance(sqs[0], MockSearchResult)) + + # Reset to default. + sqs = self.sqs.result_class(None).all() + self.assertTrue(isinstance(sqs[0], SearchResult)) + + def test_facet(self): + self.assertEqual(len(self.sqs.facet('name').facet_counts()['fields']['name']), 3) + + +class BoostMockSearchIndex(indexes.SearchIndex): + text = indexes.CharField( + document=True, use_template=True, + template_name='search/indexes/core/mockmodel_template.txt' + ) + author = indexes.CharField(model_attr='author', weight=2.0) + editor = indexes.CharField(model_attr='editor') + pub_date = indexes.DateField(model_attr='pub_date') + + def get_model(self): + return AFourthMockModel + + +class BoostFieldTestCase(HaystackBackendTestCase, TestCase): + """ + Tests boosted fields. + """ + + def get_index(self): + return BoostMockSearchIndex() + + def setUp(self): + super(BoostFieldTestCase, self).setUp() + + self.sample_objs = [] + for i in range(1, 5): + mock = AFourthMockModel() + mock.id = i + if i % 2: + mock.author = 'daniel' + mock.editor = 'david' + else: + mock.author = 'david' + mock.editor = 'daniel' + mock.pub_date = datetime.date(2009, 2, 25) - datetime.timedelta(days=i) + self.sample_objs.append(mock) + + self.backend.update(self.index, self.sample_objs) + + def test_boost(self): + sqs = SearchQuerySet() + + self.assertEqual(len(sqs.all()), 4) + + results = sqs.filter(SQ(author='daniel') | SQ(editor='daniel')) + + self.assertEqual([result.id for result in results], [ + 'core.afourthmockmodel.1', + 'core.afourthmockmodel.3', + 'core.afourthmockmodel.2', + 'core.afourthmockmodel.4' + ])