def test_with_backtrace(self): matcher = HasQueryCount(LessThan(2)) collector = RequestTimelineCollector() collector.count = 2 collector.queries = [ (0, 1, "SQL-main-slave", "SELECT 1 FROM Person", ' File "example", line 2, in <module>\n' ' Store.of(Person).one()\n'), (2, 3, "SQL-main-slave", "SELECT 1 FROM Product", ' File "example", line 3, in <module>\n' ' Store.of(Product).one()\n'), ] mismatch = matcher.match(collector) self.assertThat(mismatch, Not(Is(None))) details = mismatch.get_details() lines = [] for name, content in details.items(): self.assertEqual("queries", name) self.assertEqual("text", content.content_type.type) lines.append(''.join(content.iter_text())) separator = "-" * 70 backtrace_separator = "." * 70 expected_lines = [ '0-1@SQL-main-slave SELECT 1 FROM Person\n' + separator + '\n' + ' File "example", line 2, in <module>\n' + ' Store.of(Person).one()\n' + backtrace_separator + '\n' + '2-3@SQL-main-slave SELECT 1 FROM Product\n' + separator + '\n' + ' File "example", line 3, in <module>\n' + ' Store.of(Product).one()\n' + backtrace_separator, ] self.assertEqual(expected_lines, lines) self.assertEqual( "queries do not match: %s" % (LessThan(2).match(2).describe(),), mismatch.describe())
def test_binary_query_counts(self): query_baseline = 40 # Assess the baseline. collector = RequestTimelineCollector() collector.register() self.addCleanup(collector.unregister) ppa = self.factory.makeArchive() viewer = self.factory.makePerson() browser = self.getUserBrowser(user=viewer) with person_logged_in(viewer): # The baseline has one package, because otherwise the # short-circuit prevents the packages iteration happening at # all and we're not actually measuring scaling # appropriately. pkg = self.factory.makeBinaryPackagePublishingHistory(archive=ppa) url = canonical_url(ppa) + "/+packages" browser.open(url) self.assertThat(collector, HasQueryCount(LessThan(query_baseline))) expected_count = collector.count # Use all new objects - avoids caching issues invalidating the # gathered metrics. login(ADMIN_EMAIL) ppa = self.factory.makeArchive() viewer = self.factory.makePerson() browser = self.getUserBrowser(user=viewer) with person_logged_in(viewer): for i in range(3): pkg = self.factory.makeBinaryPackagePublishingHistory( archive=ppa, distroarchseries=pkg.distroarchseries) url = canonical_url(ppa) + "/+packages" browser.open(url) self.assertThat(collector, HasQueryCount(Equals(expected_count)))
def test_match(self): matcher = HasQueryCount(Is(3)) collector = RequestTimelineCollector() collector.count = 3 # not inspected del collector.queries self.assertThat(matcher.match(collector), Is(None))
def test_mismatch(self): matcher = HasQueryCount(LessThan(2)) collector = RequestTimelineCollector() collector.count = 2 collector.queries = [ (0, 1, "SQL-main-slave", "SELECT 1 FROM Person", None), (2, 3, "SQL-main-slave", "SELECT 1 FROM Product", None), ] mismatch = matcher.match(collector) self.assertThat(mismatch, Not(Is(None))) details = mismatch.get_details() lines = [] for name, content in details.items(): self.assertEqual("queries", name) self.assertEqual("text", content.content_type.type) lines.append(''.join(content.iter_text())) separator = "-" * 70 expected_lines = [ "0-1@SQL-main-slave SELECT 1 FROM Person\n" + separator + "\n" + "2-3@SQL-main-slave SELECT 1 FROM Product\n" + separator, ] self.assertEqual(expected_lines, lines) self.assertEqual( "queries do not match: %s" % (LessThan(2).match(2).describe(),), mismatch.describe())
def test_messages_query_counts_constant(self): # XXX Robert Collins 2010-09-15 bug=619017 # This test may be thrown off by the reference bug. To get around the # problem, flush and reset are called on the bug storm cache before # each call to the webservice. When lp's storm is updated to release # the committed fix for this bug, please see about updating this test. login(USER_EMAIL) bug = self.factory.makeBug() store = Store.of(bug) self.factory.makeBugComment(bug) self.factory.makeBugComment(bug) self.factory.makeBugComment(bug) webservice = LaunchpadWebServiceCaller('launchpad-library', 'salgado-change-anything') collector = RequestTimelineCollector() collector.register() self.addCleanup(collector.unregister) url = '/bugs/%d/messages?ws.size=75' % bug.id # First request. store.flush() store.reset() response = webservice.get(url) self.assertThat(collector, HasQueryCount(LessThan(24))) with_2_count = collector.count self.assertEqual(response.status, 200) login(USER_EMAIL) for i in range(50): self.factory.makeBugComment(bug) self.factory.makeBugAttachment(bug) logout() # Second request. store.flush() store.reset() response = webservice.get(url) self.assertThat(collector, HasQueryCount(Equals(with_2_count)))
def test_api_branches_query_count(self): webservice = LaunchpadWebServiceCaller() collector = RequestTimelineCollector() collector.register() self.addCleanup(collector.unregister) # Get 'all' of the 50 branches this collection is limited to - rather # than the default in-test-suite pagination size of 5. url = "/branches?ws.size=50" logout() response = webservice.get(url, headers={'User-Agent': 'AnonNeedsThis'}) self.assertEqual(response.status, 200, "Got %d for url %r with response %r" % ( response.status, url, response.body)) self.assertThat(collector, HasQueryCount(LessThan(17)))
def test_permission_check_query_count_for_admin_members(self): # The number of administrators a team has doesn't affect the # number of queries carried out when checking that one of those # administrators can update that team's subscriptions. team = self.factory.makeTeam() team_2 = self.factory.makeTeam() # For this test we'll create two teams, one with one # administrator and the other with several. with person_logged_in(team.teamowner): team.addMember( self.subscriber, team.teamowner, status=TeamMembershipStatus.ADMIN) self.bug.subscribe(team, team.teamowner) with person_logged_in(team_2.teamowner): for i in range(25): person = self.factory.makePerson() team_2.addMember( person, team_2.teamowner, status=TeamMembershipStatus.ADMIN) team_2.addMember( self.subscriber, team_2.teamowner, status=TeamMembershipStatus.ADMIN) self.bug.subscribe(team_2, team_2.teamowner) collector = RequestTimelineCollector() collector.register() self.addCleanup(collector.unregister) with person_logged_in(self.subscriber): self.updateBugNotificationLevelWithWebService( self.bug.id, team.name, self.subscriber) # 25 is an entirely arbitrary limit for the number of queries # this requires, based on the number run when the code was # written; it should give us a nice early warning if the number # of queries starts to grow. self.assertThat( collector, HasQueryCount(LessThan(25))) # It might seem odd that we don't do this all as one with block, # but using the collector and the webservice means our # interaction goes away, so we have to set up a new one. with person_logged_in(self.subscriber): self.updateBugNotificationLevelWithWebService( self.bug.id, team_2.name, self.subscriber) self.assertThat( collector, HasQueryCount(LessThan(25)))
def test_blueprint_listing_query_count(self): """Set a maximum number of queries for sprint blueprint lists.""" sprint = self.factory.makeSprint() for count in range(10): blueprint = self.factory.makeSpecification() link = blueprint.linkSprint(sprint, blueprint.owner) link.acceptBy(sprint.owner) with RequestTimelineCollector() as recorder: self.getViewBrowser(sprint) self.assertThat(recorder, HasQueryCount(Equals(30)))
def test_proprietary_blueprint_listing_query_count(self): """Set a maximum number of queries for sprint blueprint lists.""" sprint = self.factory.makeSprint() for count in range(10): blueprint = self.factory.makeSpecification( information_type=InformationType.PROPRIETARY) owner = removeSecurityProxy(blueprint).owner link = removeSecurityProxy(blueprint).linkSprint(sprint, owner) link.acceptBy(sprint.owner) with RequestTimelineCollector() as recorder: self.getViewBrowser(sprint) self.assertThat(recorder, HasQueryCount(Equals(22)))
def test_source_query_counts(self): query_baseline = 43 # Assess the baseline. collector = RequestTimelineCollector() collector.register() self.addCleanup(collector.unregister) ppa = self.factory.makeArchive() viewer = self.factory.makePerson() browser = self.getUserBrowser(user=viewer) with person_logged_in(viewer): # The baseline has one package, because otherwise the # short-circuit prevents the packages iteration happening at # all and we're not actually measuring scaling # appropriately. self.factory.makeSourcePackagePublishingHistory(archive=ppa) url = canonical_url(ppa) + "/+packages" browser.open(url) self.assertThat(collector, HasQueryCount(LessThan(query_baseline))) expected_count = collector.count # We scale with 1 query per distro series because of # getCurrentSourceReleases. expected_count += 1 # We need a fuzz of one because if the test is the first to run a # credentials lookup is done as well (and accrued to the collector). expected_count += 1 # Use all new objects - avoids caching issues invalidating the # gathered metrics. login(ADMIN_EMAIL) ppa = self.factory.makeArchive() viewer = self.factory.makePerson() browser = self.getUserBrowser(user=viewer) with person_logged_in(viewer): for i in range(2): pkg = self.factory.makeSourcePackagePublishingHistory( archive=ppa) self.factory.makeSourcePackagePublishingHistory( archive=ppa, distroseries=pkg.distroseries) url = canonical_url(ppa) + "/+packages" browser.open(url) self.assertThat(collector, HasQueryCount(LessThan(expected_count)))
def check_query_counts_scaling_with_unique_people(self, target, targettype): """Check that a particular hasSpecifications target scales well. :param target: A spec target like a product. :param targettype: The parameter to pass to makeSpecification to associate the target. e.g. 'product'. """ query_baseline = 40 people = [] for _ in range(10): people.append(self.factory.makePerson()) specs = [] for _ in range(10): specs.append( self.factory.makeSpecification(**{targettype: target})) collector = RequestTimelineCollector() collector.register() self.addCleanup(collector.unregister) url = canonical_url(target) + "/+assignments" viewer = self.factory.makePerson() browser = self.getUserBrowser(user=viewer) # Seed the cookie cache and any other cross-request state we may gain # in future. See lp.services.webapp.serssion: _get_secret. browser.open(url) self.invalidate_and_render(browser, target, url) # Set a baseline self.assertThat(collector, HasQueryCount(LessThan(query_baseline))) no_assignees_count = collector.count # Assign many unique people, which shouldn't change the page queries. # Due to storm bug 619017 additional queries can be triggered when # revalidating people, so we allow -some- fuzz. login(ADMIN_EMAIL) for person, spec in zip(people, specs): spec.assignee = person logout() self.invalidate_and_render(browser, target, url) self.assertThat(collector, HasQueryCount(LessThan(no_assignees_count + 5)))
def test_list(self): names = ['bob', 'frog'] for i in range(3): builder = self.factory.makeBuilder() self.factory.makeBinaryPackageBuild().queueBuild().markAsBuilding( builder) names.append(builder.name) logout() with RequestTimelineCollector() as recorder: builders = self.webservice.get('/builders', api_version='devel').jsonBody() self.assertContentEqual(names, [b['name'] for b in builders['entries']]) self.assertThat(recorder, HasQueryCount(Equals(19)))
def match(self, context): # circular dependencies. from lp.testing.pages import setupBrowserForUser with person_logged_in(self.user): context_url = canonical_url(context, view_name=self.view_name, **self.options) browser = setupBrowserForUser(self.user) flush_database_caches() with RequestTimelineCollector() as collector: browser.open(context_url) counter = HasQueryCount(LessThan(self.query_limit)) # When bug 724691 is fixed, this can become an AnnotateMismatch to # describe the object being rendered. return counter.match(collector)
def test_byEquality(self): old_collector = RequestTimelineCollector() old_collector.count = 2 old_collector.queries = [ (0, 1, "SQL-main-slave", "SELECT 1 FROM Person", None), (2, 3, "SQL-main-slave", "SELECT 1 FROM Product", None), ] new_collector = RequestTimelineCollector() new_collector.count = 3 new_collector.queries = [ (0, 1, "SQL-main-slave", "SELECT 1 FROM Person", None), (2, 3, "SQL-main-slave", "SELECT 1 FROM Product", None), (4, 5, "SQL-main-slave", "SELECT 1 FROM Distribution", None), ] matcher = HasQueryCount.byEquality(old_collector) mismatch = matcher.match(new_collector) self.assertThat(mismatch, Not(Is(None))) details = mismatch.get_details() old_lines = [] new_lines = [] self.assertThat(details, KeysEqual("queries", "other_queries")) self.assertEqual("text", details["other_queries"].content_type.type) old_lines.append("".join(details["other_queries"].iter_text())) self.assertEqual("text", details["queries"].content_type.type) new_lines.append("".join(details["queries"].iter_text())) separator = "-" * 70 expected_old_lines = [ "0-1@SQL-main-slave SELECT 1 FROM Person\n" + separator + "\n" + "2-3@SQL-main-slave SELECT 1 FROM Product\n" + separator, ] expected_new_lines = [ "0-1@SQL-main-slave SELECT 1 FROM Person\n" + separator + "\n" + "2-3@SQL-main-slave SELECT 1 FROM Product\n" + separator + "\n" + "4-5@SQL-main-slave SELECT 1 FROM Distribution\n" + separator, ] self.assertEqual(expected_old_lines, old_lines) self.assertEqual(expected_new_lines, new_lines) self.assertEqual( "queries do not match: %s" % (Equals(2).match(3).describe(),), mismatch.describe())
def test_more_private_bugs_query_count_is_constant(self): # This test tests that as we add more private bugs to a milestone # index page, the number of queries issued by the page does not # change. It also sets a cap on the queries for this page: if the # baseline were to increase, the test would fail. As the baseline # is very large already, if the test fails due to such a change, # please cut some more of the existing fat out of it rather than # increasing the cap. page_query_limit = 37 product = self.factory.makeProduct() product_owner = product.owner login_person(product.owner) milestone = self.factory.makeMilestone( productseries=product.development_focus) bug1 = self.factory.makeBug(target=product, information_type=InformationType.USERDATA, owner=product.owner) bug1.bugtasks[0].transitionToMilestone(milestone, product.owner) # We look at the page as someone who is a member of a team and the # team is subscribed to the bugs, so that we don't get trivial # shortcuts avoiding queries : test the worst case. subscribed_team = self.factory.makeTeam( membership_policy=TeamMembershipPolicy.MODERATED) viewer = self.factory.makePerson() with person_logged_in(subscribed_team.teamowner): subscribed_team.addMember(viewer, subscribed_team.teamowner) bug1.subscribe(subscribed_team, product.owner) bug1_url = canonical_url(bug1) milestone_url = canonical_url(milestone) browser = self.getUserBrowser(user=viewer) # Seed the cookie cache and any other cross-request state we may gain # in future. See lp.services.webapp.serssion: _get_secret. browser.open(milestone_url) collector = RequestTimelineCollector() collector.register() self.addCleanup(collector.unregister) browser.open(milestone_url) # Check that the test found the bug self.assertTrue(bug1_url in browser.contents) self.assertThat(collector, HasQueryCount(LessThan(page_query_limit))) with_1_private_bug = collector.count with_1_queries = [ "%s: %s" % (pos, stmt[3]) for (pos, stmt) in enumerate(collector.queries) ] login_person(product_owner) bug2 = self.factory.makeBug(target=product, information_type=InformationType.USERDATA, owner=product.owner) bug2.bugtasks[0].transitionToMilestone(milestone, product.owner) bug2.subscribe(subscribed_team, product.owner) bug2_url = canonical_url(bug2) bug3 = self.factory.makeBug(target=product, information_type=InformationType.USERDATA, owner=product.owner) bug3.bugtasks[0].transitionToMilestone(milestone, product.owner) bug3.subscribe(subscribed_team, product.owner) logout() browser.open(milestone_url) self.assertTrue(bug2_url in browser.contents) self.assertThat(collector, HasQueryCount(LessThan(page_query_limit))) with_3_private_bugs = collector.count with_3_queries = [ "%s: %s" % (pos, stmt[3]) for (pos, stmt) in enumerate(collector.queries) ] self.assertEqual( with_1_private_bug, with_3_private_bugs, "different query count: \n%s\n******************\n%s\n" % ('\n'.join(with_1_queries), '\n'.join(with_3_queries)))