def _get_product_info(self, product, href, resource, max_): penv = ProductEnvironment(self.env, product.prefix) results = [] # some queries return a list/tuple, some a generator, # hence count() to get the result length def count(iter_): try: return len(iter_) except TypeError: return sum(1 for _ in iter_) query = resource['type'].select(penv) for q in itertools.islice(query, max_): q.url = href(resource['name'], q.name) \ if resource.get('hrefurl') \ else Query.from_string(penv, '%s=%s&%s&col=%s' % (resource['name'], q.name, self.COMMON_QUERY, resource['name']) ).get_href(href) q.ticket_count = penv.db_query(""" SELECT COUNT(*) FROM ticket WHERE ticket.%s='%s' AND ticket.status <> 'closed' """ % (resource['name'], q.name))[0][0] results.append(q) # add a '(No <milestone/component/version>)' entry if there are # tickets without an assigned resource in the product ticket_count = penv.db_query( """SELECT COUNT(*) FROM ticket WHERE %s='' AND status <> 'closed'""" % (resource['name'],))[0][0] if ticket_count != 0: q = resource['type'](penv) q.name = '(No %s)' % (resource['name'],) q.url = Query.from_string(penv, 'status=!closed&col=id&col=summary&col=owner' '&col=status&col=priority&order=priority&%s=' % (resource['name'],) ).get_href(href) q.ticket_count = ticket_count results.append(q) results.sort(key=lambda x: x.ticket_count, reverse=True) # add a link to the resource list if there are # more than max resources defined if count(query) > max_: q = resource['type'](penv) q.name = _('... more') q.ticket_count = None q.url = href(resource['name']) if resource.get('hrefurl') \ else href.dashboard() results.append(q) return results
class ProductMilestoneTestCase(MilestoneTestCase, MultiproductTestCase): def setUp(self): self.global_env = self._setup_test_env(create_folder=True) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self._load_default_data(self.env) def tearDown(self): shutil.rmtree(self.global_env.path) self.global_env.reset_db() self.env = self.global_env = None def test_update_milestone(self): self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')") milestone = Milestone(self.env, 'Test') t1 = datetime(2001, 01, 01, tzinfo=utc) t2 = datetime(2002, 02, 02, tzinfo=utc) milestone.due = t1 milestone.completed = t2 milestone.description = 'Foo bar' milestone.update() self.assertEqual( [('Test', to_utimestamp(t1), to_utimestamp(t2), 'Foo bar', self.default_product)], self.env.db_query("SELECT * FROM milestone WHERE name='Test'"))
class ProductMilestoneTestCase(MilestoneTestCase, MultiproductTestCase): def setUp(self): self.global_env = self._setup_test_env(create_folder=True) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self._load_default_data(self.env) def tearDown(self): shutil.rmtree(self.global_env.path) self.global_env.reset_db() self.env = self.global_env = None @unittest.skipUnless(threading, 'Threading required for test') def test_milestone_threads(self): """ Ensure that in threaded (e.g. mod_wsgi) situations, we get an accurate list of milestones from Milestone.list The basic strategy is: thread-1 requests a list of milestones thread-2 adds a milestone thread-1 requests a new list of milestones To pass, thread-1 should have a list of milestones that matches those that are in the database. """ lock = threading.RLock() results = [] # two events to coordinate the workers and ensure that the threads # alternate appropriately e1 = threading.Event() e2 = threading.Event() def task(add): """the thread task - either we are discovering or adding events""" with lock: env = ProductEnvironment(self.global_env, self.default_product) if add: name = 'milestone_from_' + threading.current_thread().name milestone = Milestone(env) milestone.name = name milestone.insert() else: # collect the names of milestones reported by Milestone and # directly from the db - as sets to ease comparison later results.append({ 'from_t': set([m.name for m in Milestone.select(env)]), 'from_db': set([ v[0] for v in self.env.db_query( "SELECT name FROM milestone") ]) }) def worker1(): """ check milestones in this thread twice either side of ceding control to worker2 """ task(False) e1.set() e2.wait() task(False) def worker2(): """ adds a milestone when worker1 allows us to then cede control back to worker1 """ e1.wait() task(True) e2.set() t1, t2 = [threading.Thread(target=f) for f in (worker1, worker2)] t1.start() t2.start() t1.join() t2.join() r = results[-1] # note we only care about the final result self.assertEqual(r['from_t'], r['from_db']) def test_update_milestone(self): self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')") milestone = Milestone(self.env, 'Test') t1 = datetime(2001, 01, 01, tzinfo=utc) t2 = datetime(2002, 02, 02, tzinfo=utc) milestone.due = t1 milestone.completed = t2 milestone.description = 'Foo bar' milestone.update() self.assertEqual( [('Test', to_utimestamp(t1), to_utimestamp(t2), 'Foo bar', self.default_product)], self.env.db_query("SELECT * FROM milestone WHERE name='Test'"))
class ProductMilestoneTestCase(MilestoneTestCase, MultiproductTestCase): def setUp(self): self.global_env = self._setup_test_env(create_folder=True) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self._load_default_data(self.env) def tearDown(self): shutil.rmtree(self.global_env.path) self.global_env.reset_db() self.env = self.global_env = None @unittest.skipUnless(threading, 'Threading required for test') def test_milestone_threads(self): """ Ensure that in threaded (e.g. mod_wsgi) situations, we get an accurate list of milestones from Milestone.list The basic strategy is: thread-1 requests a list of milestones thread-2 adds a milestone thread-1 requests a new list of milestones To pass, thread-1 should have a list of milestones that matches those that are in the database. """ lock = threading.RLock() results = [] # two events to coordinate the workers and ensure that the threads # alternate appropriately e1 = threading.Event() e2 = threading.Event() def task(add): """the thread task - either we are discovering or adding events""" with lock: env = ProductEnvironment(self.global_env, self.default_product) if add: name = 'milestone_from_' + threading.current_thread().name milestone = Milestone(env) milestone.name = name milestone.insert() else: # collect the names of milestones reported by Milestone and # directly from the db - as sets to ease comparison later results.append({ 'from_t': set([m.name for m in Milestone.select(env)]), 'from_db': set( [v[0] for v in self.env.db_query( "SELECT name FROM milestone")])}) def worker1(): """ check milestones in this thread twice either side of ceding control to worker2 """ task(False) e1.set() e2.wait() task(False) def worker2(): """ adds a milestone when worker1 allows us to then cede control back to worker1 """ e1.wait() task(True) e2.set() t1, t2 = [threading.Thread(target=f) for f in (worker1, worker2)] t1.start() t2.start() t1.join() t2.join() r = results[-1] # note we only care about the final result self.assertEqual(r['from_t'], r['from_db']) def test_update_milestone(self): self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')") milestone = Milestone(self.env, 'Test') t1 = datetime(2001, 01, 01, tzinfo=utc) t2 = datetime(2002, 02, 02, tzinfo=utc) milestone.due = t1 milestone.completed = t2 milestone.description = 'Foo bar' milestone.update() self.assertEqual( [('Test', to_utimestamp(t1), to_utimestamp(t2), 'Foo bar', self.default_product)], self.env.db_query("SELECT * FROM milestone WHERE name='Test'"))