class MainTests(BaseTwistedTestCase): """ Basic tests to check main.Main """ @defer.inlineCallbacks def setUp(self): """ Sets up a test. """ yield super(MainTests, self).setUp() self.root = self.mktemp('root') self.shares = self.mktemp('shares') self.data = self.mktemp('data') self.partials_dir = self.mktemp('partials_dir') self.patch(main_mod, 'SyncdaemonService', FakedExternalInterface) # no status listener by default self.patch(main_mod.status_listener, "get_listener", lambda *a: None) self.handler = MementoHandler() self.handler.setLevel(logging.DEBUG) self._logger = logging.getLogger('ubuntuone.SyncDaemon') self._logger.addHandler(self.handler) self.addCleanup(self._logger.removeHandler, self.handler) def _get_main_common_params(self): """Return the parameters used by the all platforms.""" return dict(root_dir=self.root, shares_dir=self.shares, data_dir=self.data, partials_dir=self.partials_dir, host='localhost', port=0, dns_srv=False, ssl=False, mark_interval=60, handshake_timeout=2, auth_credentials=FAKED_CREDENTIALS, monitor_class=FakeMonitor) def build_main(self, **kwargs): """Build and return a Main object. Use reasonable defaults for the tests, plus whatever extra kwargs are passed in. """ # get the params using the platform code to ensure they are correct params = self._get_main_common_params() params.update(kwargs) m = main_mod.Main(**params) self.addCleanup(m.shutdown) m.local_rescan = lambda *_: m.event_q.push('SYS_LOCAL_RESCAN_DONE') return m def test_main_initialization(self): """test that creating a Main instance works as expected.""" main = self.build_main() self.assertIsInstance(main, main_mod.Main) def test_main_start(self): """Test that Main.start works.""" main = self.build_main() main.start() def test_main_restarts_on_critical_error(self): """Test that Main restarts when syncdaemon gets into UNKNOWN_ERROR.""" self.restarted = False main = self.build_main() main.restart = lambda: setattr(self, 'restarted', True) main.start() main.event_q.push('SYS_UNKNOWN_ERROR') self.assertTrue(self.restarted) @defer.inlineCallbacks def test_shutdown_pushes_sys_quit(self): """When shutting down, the SYS_QUIT event is pushed.""" params = self._get_main_common_params() main = main_mod.Main(**params) events = [] self.patch(main.event_q, 'push', lambda *a, **kw: events.append((a, kw))) yield main.shutdown() expected = [(('SYS_USER_DISCONNECT',), {}), (('SYS_QUIT',), {})] self.assertEqual(expected, events) def test_handshake_timeout(self): """Check connecting times out.""" d0 = defer.Deferred() class Handler: """Trivial event handler.""" def handle_SYS_HANDSHAKE_TIMEOUT(self): """Pass the test when we get this event.""" reactor.callLater(0, d0.callback, None) main = self.build_main(handshake_timeout=0) def fake_connect(*a): """Only connect when States told so.""" main.event_q.push('SYS_CONNECTION_MADE') return defer.Deferred() main.action_q.connect = fake_connect # fake the following to not be executed main.get_root = lambda *_: defer.Deferred() main.action_q.check_version = lambda *_: defer.Deferred() main.event_q.subscribe(Handler()) main.start() main.event_q.push('SYS_NET_CONNECTED') main.event_q.push('SYS_USER_CONNECT', access_token='') return d0 def test_create_dirs_already_exists_dirs(self): """test that creating a Main instance works as expected.""" link = os.path.join(self.root, 'Shared With Me') self.assertFalse(is_link(link)) self.assertTrue(path_exists(self.shares)) self.assertTrue(path_exists(self.root)) main = self.build_main() # check that the shares link is actually a link self.assertTrue(is_link(main.shares_dir_link)) self.assertEquals(link, main.shares_dir_link) def test_create_dirs_already_exists_symlink_too(self): """test that creating a Main instance works as expected.""" link = os.path.join(self.root, 'Shared With Me') make_link(self.shares, link) self.assertTrue(is_link(link)) self.assertTrue(path_exists(self.shares)) self.assertTrue(path_exists(self.root)) main = self.build_main() # check that the shares link is actually a link self.assertTrue(is_link(main.shares_dir_link)) def test_create_dirs_already_exists_but_not_symlink(self): """test that creating a Main instance works as expected.""" link = os.path.join(self.root, 'Shared With Me') make_dir(link, recursive=True) self.assertTrue(path_exists(link)) self.assertFalse(is_link(link)) self.assertTrue(path_exists(self.shares)) self.assertTrue(path_exists(self.root)) main = self.build_main() # check that the shares link is actually a link self.assertEquals(main.shares_dir_link, link) self.assertFalse(is_link(main.shares_dir_link)) def test_create_dirs_none_exists(self): """test that creating a Main instance works as expected.""" # remove the existing dirs remove_dir(self.root) remove_dir(self.shares) main = self.build_main() # check that the shares link is actually a link self.assertTrue(is_link(main.shares_dir_link)) self.assertTrue(path_exists(self.shares)) self.assertTrue(path_exists(self.root)) def test_connect_if_autoconnect_is_enabled(self): """If autoconnect option is enabled, connect the syncdaemon.""" user_config = main_mod.config.get_user_config() orig = user_config.get_autoconnect() user_config.set_autoconnect(True) self.addCleanup(user_config.set_autoconnect, orig) main = self.build_main() expected = [('connect', (), {'autoconnecting': True})] self.assertEqual(main.external._called, expected) def test_dont_connect_if_autoconnect_is_disabled(self): """If autoconnect option is disabled, do not connect the syncdaemon.""" user_config = main_mod.config.get_user_config() orig = user_config.get_autoconnect() user_config.set_autoconnect(False) self.addCleanup(user_config.set_autoconnect, orig) main = self.build_main() self.assertEqual(main.external._called, []) def _get_listeners(self, main): """Return the subscribed objects.""" s = set() for listener in main.event_q.listener_map.values(): for x in listener: s.add(x) return s def test_status_listener_is_installed(self): """The status listener is installed if needed.""" self.patch(main_mod.status_listener, "get_listener", lambda *a: FakeListener()) main = self.build_main() self.assertIn(main.status_listener, self._get_listeners(main)) def test_status_listener_not_installed_when_disabled(self): """The status listener is not started if it's not available.""" main = self.build_main() self.assertNotIn(main.status_listener, self._get_listeners(main)) def test_get_homedir(self): """The get_homedir returns the root dir.""" self.patch(main_mod, "user_home", self.home_dir) expected = expand_user('~') main = self.build_main() self.assertEqual(main.get_homedir(), expected) def test_get_rootdir(self): """The get_rootdir returns the root dir.""" expected = expand_user(os.path.join('~', 'Ubuntu Test One')) main = self.build_main(root_dir=expected) self.assertEqual(main.get_rootdir(), expected) def test_get_sharesdir(self): """The get_sharesdir returns the shares dir.""" expected = expand_user(os.path.join('~', 'Share it to Me')) main = self.build_main(shares_dir=expected) self.assertEqual(main.get_sharesdir(), expected) def test_get_sharesdirlink(self): """The get_sharesdirlink returns the shares dir link.""" expected = 'Share it to Me' main = self.build_main(shares_symlink_name=expected) self.assertEqual(main.get_sharesdir_link(), os.path.join(main.get_rootdir(), expected)) def test_version_is_logged(self): """Test that the client version is logged.""" self.build_main() self.assertTrue(self.handler.check_info("client version", VERSION)) def test_mark(self): """Check the MARK logs ok.""" main = self.build_main() main.log_mark() shouldlog = ('MARK', "State: 'INIT'", 'queues IDLE', 'connection', 'queue: 0', 'offloaded: 0', 'hash: 0') self.assertTrue(self.handler.check(NOTE, *shouldlog))
class ReactorInspectorTestCase(TwistedTestCase): """Test the ReactorInspector class.""" def setUp(self): """Set up.""" class Helper(object): """Fake object with a controllable call.""" def __init__(self): self.call_count = 1 self.calls = [] self.ri = None def call(self, func): """Call function when counter is 0, then stop running.""" self.call_count -= 1 self.calls.append(func) if self.call_count == 0: for f in self.calls: f() if self.call_count <= 0: self.ri.stop() class FakeMetrics(object): """Fake Metrics object that records calls.""" def __init__(self): """Initialize calls.""" self.calls = [] def meter(self, name, count): """Record call to meter().""" self.calls.append(("meter", name, count)) def gauge(self, name, val): """Record call to gauge().""" self.calls.append(("gauge", name, val)) logger = logging.getLogger("storage.server") logger.propagate = False logger.setLevel(TRACE) self.handler = MementoHandler() self.handler.setLevel(TRACE) logger.addHandler(self.handler) self.addCleanup(logger.removeHandler, self.handler) self.helper = Helper() self.fake_metrics = FakeMetrics() MetricsConnector.register_metrics("reactor_inspector", instance=self.fake_metrics) self.addCleanup(MetricsConnector.unregister_metrics) self.ri = ReactorInspector(logger, self.helper.call, loop_time=.1) self.helper.ri = self.ri def run_ri(self, call_count=None, join=True): """Set the call count and then run the ReactorInspector.""" if call_count is not None: self.helper.call_count = call_count self.start_ts = time.time() self.ri.start() # Reactor will stop after call_count calls, thanks to helper if join: self.ri.join() def test_stop(self): """It stops.""" self.run_ri(1000, join=False) assert self.ri.is_alive() self.ri.stop() self.ri.join() self.assertFalse(self.ri.is_alive()) @defer.inlineCallbacks def test_dump_frames(self): """Test how frames are dumped. Rules: - own frame must not be logged - must log all other threads - main reactor thread must have special title """ # other thread, whose frame must be logged waitingd = defer.Deferred() def waiting_function(): """Function with funny name to be checked later.""" reactor.callFromThread(waitingd.callback, True) # wait have a default value event.wait() event = threading.Event() threading.Thread(target=waiting_function).start() # Make sure the thread has entered the waiting_function yield waitingd # Set reactor_thread since we're not starting the ReactorInspector # thread here. self.ri.reactor_thread = threading.currentThread().ident # dump frames in other thread, also def dumping_function(): """Function with funny name to be checked later.""" time.sleep(.1) self.ri.dump_frames() reactor.callFromThread(d.callback, True) d = defer.Deferred() threading.Thread(target=dumping_function).start() yield d event.set() # check self.assertFalse(self.handler.check_debug("dumping_function")) self.assertTrue(self.handler.check_debug("Dumping Python frame", "waiting_function")) self.assertTrue(self.handler.check_debug("Dumping Python frame", "reactor main thread")) def test_reactor_ok(self): """Reactor working fast.""" self.run_ri() ok_line = self.handler.check(TRACE, "ReactorInspector: ok") self.assertTrue(ok_line) self.assertTrue(ok_line.args[-1] >= 0) # Should be near zero delay # Check the metrics expected_metric = ("gauge", "delay", ok_line.args[-1]) self.assertEqual([expected_metric], self.fake_metrics.calls) self.assertTrue(self.ri.last_responsive_ts >= self.start_ts) @defer.inlineCallbacks def test_reactor_blocked(self): """Reactor not working fast.""" dump_frames_called = defer.Deferred() self.ri.dump_frames = lambda: dump_frames_called.callback(True) self.run_ri(0) yield dump_frames_called log_line = self.handler.check(logging.CRITICAL, "ReactorInspector", "detected unresponsive") self.assertTrue(log_line) self.assertTrue(log_line.args[-1] >= .1) # waited for entire loop time # Check the metrics expected_metric = ("gauge", "delay", log_line.args[-1]) self.assertEqual([expected_metric], self.fake_metrics.calls) self.assertTrue(self.ri.last_responsive_ts < self.start_ts) def test_reactor_back_alive(self): """Reactor resurrects after some loops.""" self.run_ri(3) late_line = self.handler.check_warning("ReactorInspector: late", "got: 0") self.assertTrue(late_line) self.assertTrue(late_line.args[-1] >= .2) # At least 2 cycles of delay # Check the metrics expected_metric = ("gauge", "delay", late_line.args[-1]) self.assertEqual(expected_metric, self.fake_metrics.calls[-1]) self.assertTrue(self.ri.queue.empty()) # A late reactor is not considered responsive (until a successful loop) self.assertTrue(self.ri.last_responsive_ts < self.start_ts)