| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The LUCI Authors. All rights reserved. | 2 # Copyright 2014 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 import datetime | 6 import datetime |
| 7 import inspect | 7 import inspect |
| 8 import logging | 8 import logging |
| 9 import os | 9 import os |
| 10 import random | 10 import random |
| 11 import sys | 11 import sys |
| 12 import unittest | 12 import unittest |
| 13 | 13 |
| 14 import test_env | 14 import test_env |
| 15 test_env.setup_test_env() | 15 test_env.setup_test_env() |
| 16 | 16 |
| 17 from google.appengine.api import datastore_errors | 17 from google.appengine.api import datastore_errors |
| 18 from google.appengine.api import search | |
| 19 from google.appengine.ext import deferred | 18 from google.appengine.ext import deferred |
| 20 from google.appengine.ext import ndb | 19 from google.appengine.ext import ndb |
| 21 | 20 |
| 22 import webtest | 21 import webtest |
| 23 | 22 |
| 24 from components import auth_testing | 23 from components import auth_testing |
| 25 from components import datastore_utils | 24 from components import datastore_utils |
| 26 from components import pubsub | 25 from components import pubsub |
| 27 from components import stats_framework | 26 from components import stats_framework |
| 28 from components import utils | 27 from components import utils |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 93 reaped_request, run_result = task_scheduler.bot_reap_task( | 92 reaped_request, run_result = task_scheduler.bot_reap_task( |
| 94 {'OS': 'Windows-3.1.1', u'pool': u'default'}, 'localhost', 'abc', None) | 93 {'OS': 'Windows-3.1.1', u'pool': u'default'}, 'localhost', 'abc', None) |
| 95 return run_result | 94 return run_result |
| 96 | 95 |
| 97 | 96 |
| 98 class TaskSchedulerApiTest(test_case.TestCase): | 97 class TaskSchedulerApiTest(test_case.TestCase): |
| 99 APP_DIR = test_env.APP_DIR | 98 APP_DIR = test_env.APP_DIR |
| 100 | 99 |
| 101 def setUp(self): | 100 def setUp(self): |
| 102 super(TaskSchedulerApiTest, self).setUp() | 101 super(TaskSchedulerApiTest, self).setUp() |
| 103 self.testbed.init_search_stub() | |
| 104 | |
| 105 self.now = datetime.datetime(2014, 1, 2, 3, 4, 5, 6) | 102 self.now = datetime.datetime(2014, 1, 2, 3, 4, 5, 6) |
| 106 self.mock_now(self.now) | 103 self.mock_now(self.now) |
| 107 self.app = webtest.TestApp( | 104 self.app = webtest.TestApp( |
| 108 deferred.application, | 105 deferred.application, |
| 109 extra_environ={ | 106 extra_environ={ |
| 110 'REMOTE_ADDR': '1.0.1.2', | 107 'REMOTE_ADDR': '1.0.1.2', |
| 111 'SERVER_SOFTWARE': os.environ['SERVER_SOFTWARE'], | 108 'SERVER_SOFTWARE': os.environ['SERVER_SOFTWARE'], |
| 112 }) | 109 }) |
| 113 self.mock(stats_framework, 'add_entry', self._parse_line) | 110 self.mock(stats_framework, 'add_entry', self._parse_line) |
| 114 auth_testing.mock_get_current_identity(self) | 111 auth_testing.mock_get_current_identity(self) |
| (...skipping 1512 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1627 } | 1624 } |
| 1628 _request, run_result = task_scheduler.bot_reap_task( | 1625 _request, run_result = task_scheduler.bot_reap_task( |
| 1629 bot_dimensions, 'localhost', 'abc', None) | 1626 bot_dimensions, 'localhost', 'abc', None) |
| 1630 self.assertEqual(1, run_result.try_number) | 1627 self.assertEqual(1, run_result.try_number) |
| 1631 self.assertEqual(task_result.State.RUNNING, run_result.state) | 1628 self.assertEqual(task_result.State.RUNNING, run_result.state) |
| 1632 self.mock_now(self.now + task_result.BOT_PING_TOLERANCE, 601) | 1629 self.mock_now(self.now + task_result.BOT_PING_TOLERANCE, 601) |
| 1633 self.assertEqual( | 1630 self.assertEqual( |
| 1634 (['1d69b9f088008811'], 0, 0), | 1631 (['1d69b9f088008811'], 0, 0), |
| 1635 task_scheduler.cron_handle_bot_died('f.local')) | 1632 task_scheduler.cron_handle_bot_died('f.local')) |
| 1636 | 1633 |
| 1637 def test_search_by_name(self): | |
| 1638 # This is awkward but it's because _search_by_name() depends on | |
| 1639 # functionality saved by task_scheduler. (There's a layering issue). | |
| 1640 data = _gen_request( | |
| 1641 properties={ | |
| 1642 'dimensions': {u'OS': u'Windows-3.1.1', u'pool': u'default'}, | |
| 1643 }) | |
| 1644 request = task_request.make_request(data, True) | |
| 1645 result_summary = task_scheduler.schedule_request(request) | |
| 1646 | |
| 1647 # Assert that search is not case-sensitive by using unexpected casing. | |
| 1648 actual, _cursor = task_result._search_by_name('requEST', None, 10) | |
| 1649 self.assertEqual([result_summary], actual) | |
| 1650 actual, _cursor = task_result._search_by_name('name', None, 10) | |
| 1651 self.assertEqual([result_summary], actual) | |
| 1652 | |
| 1653 def test_search_by_name_failures(self): | |
| 1654 data = _gen_request( | |
| 1655 properties={ | |
| 1656 'dimensions': {u'OS': u'Windows-3.1.1', u'pool': u'default'}, | |
| 1657 }) | |
| 1658 request = task_request.make_request(data, True) | |
| 1659 result_summary = task_scheduler.schedule_request(request) | |
| 1660 | |
| 1661 actual, _cursor = task_result._search_by_name('foo', None, 10) | |
| 1662 self.assertEqual([], actual) | |
| 1663 # Partial match doesn't work. | |
| 1664 actual, _cursor = task_result._search_by_name('nam', None, 10) | |
| 1665 self.assertEqual([], actual) | |
| 1666 | |
| 1667 def test_search_by_name_broken_tasks(self): | |
| 1668 # Create tasks where task_scheduler.schedule_request() fails in the middle. | |
| 1669 # This is done by mocking the functions to fail every SKIP call and running | |
| 1670 # it in a loop. | |
| 1671 class RandomFailure(Exception): | |
| 1672 pass | |
| 1673 | |
| 1674 # First call fails ndb.put_multi(), second call fails search.Index.put(), | |
| 1675 # third call work. | |
| 1676 index = [0] | |
| 1677 SKIP = 3 | |
| 1678 def put_multi(*args, **kwargs): | |
| 1679 callers = [i[3] for i in inspect.stack()] | |
| 1680 self.assertTrue( | |
| 1681 'make_request' in callers or 'schedule_request' in callers, callers) | |
| 1682 if (index[0] % SKIP) == 1: | |
| 1683 raise RandomFailure() | |
| 1684 return old_put_multi(*args, **kwargs) | |
| 1685 | |
| 1686 def put_async(*args, **kwargs): | |
| 1687 callers = [i[3] for i in inspect.stack()] | |
| 1688 self.assertIn('schedule_request', callers) | |
| 1689 out = ndb.Future() | |
| 1690 if (index[0] % SKIP) == 2: | |
| 1691 out.set_exception(search.Error()) | |
| 1692 else: | |
| 1693 out.set_result(old_put_async(*args, **kwargs).get_result()) | |
| 1694 return out | |
| 1695 | |
| 1696 old_put_multi = self.mock(ndb, 'put_multi', put_multi) | |
| 1697 old_put_async = self.mock(search.Index, 'put_async', put_async) | |
| 1698 | |
| 1699 saved = [] | |
| 1700 | |
| 1701 for i in xrange(100): | |
| 1702 index[0] = i | |
| 1703 data = _gen_request( | |
| 1704 name='Request %d' % i, | |
| 1705 properties={ | |
| 1706 'dimensions': {u'OS': u'Windows-3.1.1', u'pool': u'default'}, | |
| 1707 }) | |
| 1708 try: | |
| 1709 request = task_request.make_request(data, True) | |
| 1710 result_summary = task_scheduler.schedule_request(request) | |
| 1711 saved.append(result_summary) | |
| 1712 except RandomFailure: | |
| 1713 pass | |
| 1714 | |
| 1715 self.assertEqual(67, len(saved)) | |
| 1716 self.assertEqual(100, task_request.TaskRequest.query().count()) | |
| 1717 self.assertEqual(67, task_result.TaskResultSummary.query().count()) | |
| 1718 | |
| 1719 # Now the DB is full of half-corrupted entities. | |
| 1720 cursor = None | |
| 1721 actual, cursor = task_result._search_by_name('Request', cursor, 31) | |
| 1722 self.assertEqual(31, len(actual)) | |
| 1723 actual, cursor = task_result._search_by_name('Request', cursor, 31) | |
| 1724 self.assertEqual(3, len(actual)) | |
| 1725 actual, cursor = task_result._search_by_name('Request', cursor, 31) | |
| 1726 self.assertEqual(0, len(actual)) | |
| 1727 | |
| 1728 | 1634 |
| 1729 if __name__ == '__main__': | 1635 if __name__ == '__main__': |
| 1730 if '-v' in sys.argv: | 1636 if '-v' in sys.argv: |
| 1731 unittest.TestCase.maxDiff = None | 1637 unittest.TestCase.maxDiff = None |
| 1732 logging.basicConfig( | 1638 logging.basicConfig( |
| 1733 level=logging.DEBUG if '-v' in sys.argv else logging.CRITICAL) | 1639 level=logging.DEBUG if '-v' in sys.argv else logging.CRITICAL) |
| 1734 unittest.main() | 1640 unittest.main() |
| OLD | NEW |