def _read_and_clear_state(self): if self.CHARM_STATE_FILE.stat().st_size: storage = SQLiteStorage(self.CHARM_STATE_FILE) with (self.JUJU_CHARM_DIR / 'metadata.yaml').open() as m: af = (self.JUJU_CHARM_DIR / 'actions.yaml') if af.exists(): with af.open() as a: meta = CharmMeta.from_yaml(m, a) else: meta = CharmMeta.from_yaml(m) framework = Framework(storage, self.JUJU_CHARM_DIR, meta, None) class ThisCharmEvents(CharmEvents): pass class Charm(self.charm_module.Charm): on = ThisCharmEvents() mycharm = Charm(framework) stored = mycharm._stored # Override the saved data with a cleared state storage.save_snapshot(stored._data.handle.path, {}) storage.commit() framework.close() else: stored = StoredStateData(None, None) return stored
def setUp(self): tmpdir = Path(tempfile.mkdtemp()) self.addCleanup(shutil.rmtree, str(tmpdir)) self.framework = Framework(tmpdir / "framework.data", tmpdir, None, None) self.addCleanup(self.framework.close)
def create_framework(self): framework = Framework(self.tmpdir / "framework.data", self.tmpdir, CharmMeta(), None) # Ensure that the Framework object is closed and cleaned up even # when the test fails or errors out. self.addCleanup(framework.close) return framework
def create_framework(self): model = create_autospec(Model) model.unit = create_autospec(Unit) model.unit.is_leader = MagicMock(return_value=False) model.app = create_autospec(Application) model.pod = create_autospec(Pod) model.config = create_autospec(ConfigData) raw_meta = {'provides': {'mongo': {"interface": "mongodb"}}} framework = Framework( self.tmpdir / "framework.data.{}".format(str(uuid4)), self.tmpdir, CharmMeta(raw=raw_meta), model) framework.model.app.name = "test-app" self.addCleanup(framework.close) return framework
def create_framework(self, *, model=None, tmpdir=None): """Create a Framework object. By default operate in-memory; pass a temporary directory via the 'tmpdir' parameter if you whish to instantiate several frameworks sharing the same dir (e.g. for storing state). """ if tmpdir is None: data_fpath = ":memory:" charm_dir = 'non-existant' else: data_fpath = tmpdir / "framework.data" charm_dir = tmpdir framework = Framework(data_fpath, charm_dir, meta=None, model=model) self.addCleanup(framework.close) return framework
def create_framework(self): model = Model(self.meta, _ModelBackend('local/0')) framework = Framework(self.tmpdir / "framework.data", self.tmpdir, self.meta, model) self.addCleanup(framework.close) return framework
def create_framework(self): framework = Framework(self.tmpdir / "framework.data", self.tmpdir, None, None) self.addCleanup(framework.close) return framework
def create_framework(self): model = Model(self.meta, _ModelBackend('local/0')) framework = Framework(SQLiteStorage(':memory:'), self.tmpdir, self.meta, model) self.addCleanup(framework.close) return framework
class BreakpointTests(unittest.TestCase): def setUp(self): tmpdir = Path(tempfile.mkdtemp()) self.addCleanup(shutil.rmtree, str(tmpdir)) self.framework = Framework(tmpdir / "framework.data", tmpdir, None, None) self.addCleanup(self.framework.close) def test_ignored(self, fake_stderr): # It doesn't do anything really unless proper environment is there. assert 'JUJU_DEBUG_AT' not in os.environ with patch('pdb.Pdb.set_trace') as mock: self.framework.breakpoint() self.assertEqual(mock.call_count, 0) self.assertEqual(fake_stderr.getvalue(), "") def test_pdb_properly_called(self, fake_stderr): # The debugger needs to leave the user in the frame where the breakpoint is executed, # which for the test is the frame we're calling it here in the test :). with patch.dict(os.environ, {'JUJU_DEBUG_AT': 'all'}): with patch('pdb.Pdb.set_trace') as mock: this_frame = inspect.currentframe() self.framework.breakpoint() self.assertEqual(mock.call_count, 1) self.assertEqual(mock.call_args, ((this_frame, ), {})) def test_welcome_message(self, fake_stderr): # Check that an initial message is shown to the user when code is interrupted. with patch.dict(os.environ, {'JUJU_DEBUG_AT': 'all'}): with patch('pdb.Pdb.set_trace'): self.framework.breakpoint() self.assertEqual(fake_stderr.getvalue(), _BREAKPOINT_WELCOME_MESSAGE) def test_welcome_message_not_multiple(self, fake_stderr): # Check that an initial message is NOT shown twice if the breakpoint is exercised # twice in the same run. with patch.dict(os.environ, {'JUJU_DEBUG_AT': 'all'}): with patch('pdb.Pdb.set_trace'): self.framework.breakpoint() self.assertEqual(fake_stderr.getvalue(), _BREAKPOINT_WELCOME_MESSAGE) self.framework.breakpoint() self.assertEqual(fake_stderr.getvalue(), _BREAKPOINT_WELCOME_MESSAGE) def test_builtin_breakpoint_hooked(self, fake_stderr): # Verify that the proper hook is set. with patch.dict(os.environ, {'JUJU_DEBUG_AT': 'all'}): with patch('pdb.Pdb.set_trace') as mock: # Calling through sys, not breakpoint() directly, so we can run the # tests with Py < 3.7. sys.breakpointhook() self.assertEqual(mock.call_count, 1) def test_breakpoint_names(self, fake_stderr): # Name rules: # - must start and end with lowercase alphanumeric characters # - only contain lowercase alphanumeric characters, or the hyphen "-" good_names = [ 'foobar', 'foo-bar-baz', 'foo-------bar', 'foo123', '778', '77-xx', 'a-b', 'ab', 'x', ] for name in good_names: with self.subTest(name=name): self.framework.breakpoint(name) bad_names = [ '', '.', '-', '...foo', 'foo.bar', 'bar--' 'FOO', 'FooBar', 'foo bar', 'foo_bar', '/foobar', 'break-here-☚', ] msg = 'breakpoint names must look like "foo" or "foo-bar"' for name in bad_names: with self.subTest(name=name): with self.assertRaises(ValueError) as cm: self.framework.breakpoint(name) self.assertEqual(str(cm.exception), msg) reserved_names = [ 'all', 'hook', ] msg = 'breakpoint names "all" and "hook" are reserved' for name in reserved_names: with self.subTest(name=name): with self.assertRaises(ValueError) as cm: self.framework.breakpoint(name) self.assertEqual(str(cm.exception), msg) not_really_names = [ 123, 1.1, False, ] for name in not_really_names: with self.subTest(name=name): with self.assertRaises(TypeError) as cm: self.framework.breakpoint(name) self.assertEqual(str(cm.exception), 'breakpoint names must be strings') def check_trace_set(self, envvar_value, breakpoint_name, call_count): """Helper to check the diverse combinations of situations.""" with patch.dict(os.environ, {'JUJU_DEBUG_AT': envvar_value}): with patch('pdb.Pdb.set_trace') as mock: self.framework.breakpoint(breakpoint_name) self.assertEqual(mock.call_count, call_count) def test_unnamed_indicated_all(self, fake_stderr): # If 'all' is indicated, unnamed breakpoints will always activate. self.check_trace_set('all', None, 1) def test_unnamed_indicated_hook(self, fake_stderr): # Special value 'hook' was indicated, nothing to do with any call. self.check_trace_set('hook', None, 0) def test_named_indicated_specifically(self, fake_stderr): # Some breakpoint was indicated, and the framework call used exactly that name. self.check_trace_set('mybreak', 'mybreak', 1) def test_named_indicated_somethingelse(self, fake_stderr): # Some breakpoint was indicated, but the framework call was not with that name. self.check_trace_set('some-breakpoint', None, 0) def test_named_indicated_ingroup(self, fake_stderr): # A multiple breakpoint was indicated, and the framework call used a name among those. self.check_trace_set('some,mybreak,foobar', 'mybreak', 1) def test_named_indicated_all(self, fake_stderr): # The framework indicated 'all', which includes any named breakpoint set. self.check_trace_set('all', 'mybreak', 1) def test_named_indicated_hook(self, fake_stderr): # The framework indicated the special value 'hook', nothing to do with any named call. self.check_trace_set('hook', 'mybreak', 0)