diff --git a/CHANGES.md b/CHANGES.md
index c0049d3..07774d9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,7 @@
+# 2.11.0
+
+* Added `ActionEvent.id`, exposing the JUJU_ACTION_UUID environment variable.
+
 # 2.10.0
 
 * Added support for Pebble Notices (`PebbleCustomNoticeEvent`, `get_notices`, and so on)
diff --git a/ops/charm.py b/ops/charm.py
index 4496383..cb77f59 100644
--- a/ops/charm.py
+++ b/ops/charm.py
@@ -125,9 +125,16 @@ class ActionEvent(EventBase):
     :meth:`log`.
     """
 
+    id: str = ""
+    """The Juju ID of the action invocation."""
+
     params: Dict[str, Any]
     """The parameters passed to the action."""
 
+    def __init__(self, handle: 'Handle', id: Optional[str] = None):
+        super().__init__(handle)
+        self.id = id  # type: ignore (for backwards compatibility)
+
     def defer(self) -> NoReturn:
         """Action events are not deferrable like other events.
 
@@ -144,10 +151,18 @@ class ActionEvent(EventBase):
 
         Not meant to be called directly by charm code.
         """
+        self.id = cast(str, snapshot['id'])
         # Params are loaded at restore rather than __init__ because
         # the model is not available in __init__.
         self.params = self.framework.model._backend.action_get()
 
+    def snapshot(self) -> Dict[str, Any]:
+        """Used by the framework to serialize the event to disk.
+
+        Not meant to be called by charm code.
+        """
+        return {'id': self.id}
+
     def set_results(self, results: Dict[str, Any]):
         """Report the result of the action.
 
diff --git a/ops/framework.py b/ops/framework.py
index 717c74c..eb3b1c3 100644
--- a/ops/framework.py
+++ b/ops/framework.py
@@ -1057,10 +1057,10 @@ class BoundStoredState:
     if TYPE_CHECKING:
         # to help the type checker and IDEs:
         @property
-        def _data(self) -> StoredStateData: ...  # noqa, type: ignore
+        def _data(self) -> StoredStateData: ...  # type: ignore
 
         @property
-        def _attr_name(self) -> str: ...  # noqa, type: ignore
+        def _attr_name(self) -> str: ...  # type: ignore
 
     def __init__(self, parent: Object, attr_name: str):
         parent.framework.register_type(StoredStateData, parent)
diff --git a/ops/main.py b/ops/main.py
index 04c756c..6f7b893 100644
--- a/ops/main.py
+++ b/ops/main.py
@@ -191,6 +191,9 @@ def _get_event_args(charm: 'ops.charm.CharmBase',
         storage = cast(Union[ops.storage.JujuStorage, ops.storage.SQLiteStorage], storage)
         storage.location = storage_location  # type: ignore
         return [storage], {}
+    elif issubclass(event_type, ops.charm.ActionEvent):
+        args: List[Any] = [os.environ['JUJU_ACTION_UUID']]
+        return args, {}
     elif issubclass(event_type, ops.charm.RelationEvent):
         relation_name = os.environ['JUJU_RELATION']
         relation_id = _get_juju_relation_id()

diff --git a/ops/testing.py b/ops/testing.py
index 0d782a6..5d509cb 100644
--- a/ops/testing.py
+++ b/ops/testing.py
@@ -246,6 +246,7 @@ class Harness(Generic[CharmType]):
         self._unit_name: str = f"{self._meta.name}/0"
         self._hooks_enabled: bool = True
         self._relation_id_counter: int = 0
+        self._action_id_counter: int = 0
         config_ = self._get_config(config)
         self._backend = _TestingModelBackend(self._unit_name, self._meta, config_)
         self._model = model.Model(self._meta, self._backend)
@@ -1883,7 +1884,8 @@ class Harness(Generic[CharmType]):
         action_under_test = _RunningAction(action_name, ActionOutput([], {}), params)
         handler = getattr(self.charm.on, f"{action_name.replace('-', '_')}_action")
         self._backend._running_action = action_under_test
-        handler.emit()
+        self._action_id_counter += 1
+        handler.emit(str(self._action_id_counter))
         self._backend._running_action = None
         if action_under_test.failure_message is not None:
             raise ActionFailed(
diff --git a/test/test_charm.py b/test/test_charm.py
index ac14784..627d2d5 100644
--- a/test/test_charm.py
+++ b/test/test_charm.py
@@ -463,7 +463,7 @@ start:
             def _on_foo_bar_action(self, event: ops.ActionEvent):
                 self.seen_action_params = event.params
                 event.log('test-log')
-                event.set_results({'res': 'val with spaces'})
+                event.set_results({'res': 'val with spaces', 'id': event.id})
                 event.fail('test-fail')
 
             def _on_start_action(self, event: ops.ActionEvent):
@@ -477,12 +477,13 @@ start:
         self.assertIn('foo_bar_action', events)
         self.assertIn('start_action', events)
 
-        charm.on.foo_bar_action.emit()
+        action_id = "1234"
+        charm.on.foo_bar_action.emit(id=action_id)
         self.assertEqual(charm.seen_action_params, {"foo-name": "name", "silent": True})
         self.assertEqual(fake_script_calls(self), [
             ['action-get', '--format=json'],
             ['action-log', "test-log"],
-            ['action-set', "res=val with spaces"],
+            ['action-set', "res=val with spaces", f"id={action_id}"],
             ['action-fail', "test-fail"],
         ])
 
@@ -511,7 +512,7 @@ start:
             charm.res = bad_res
 
             with self.assertRaises(ValueError):
-                charm.on.foo_bar_action.emit()
+                charm.on.foo_bar_action.emit(id='1')
 
     def _test_action_event_defer_fails(self, cmd_type: str):
 
@@ -532,7 +533,7 @@ start:
         charm = MyCharm(framework)
 
         with self.assertRaises(RuntimeError):
-            charm.on.start_action.emit()
+            charm.on.start_action.emit(id='2')
 
     def test_action_event_defer_fails(self):
         self._test_action_event_defer_fails('action')
diff --git a/test/test_framework.py b/test/test_framework.py
index e7c7a20..7a2a778 100644
--- a/test/test_framework.py
+++ b/test/test_framework.py
@@ -1813,7 +1813,7 @@ class DebugHookTests(BaseTestCase):
 
         with patch('sys.stderr', new_callable=io.StringIO):
             with patch('pdb.runcall') as mock:
-                publisher.foobar_action.emit()
+                publisher.foobar_action.emit(id='1')
 
         self.assertEqual(mock.call_count, 1)
         self.assertFalse(observer.called)
@@ -1833,7 +1833,7 @@ class DebugHookTests(BaseTestCase):
 
         with patch('sys.stderr', new_callable=io.StringIO):
             with patch('pdb.runcall') as mock:
-                publisher.foobar_action.emit()
+                publisher.foobar_action.emit(id='2')
 
         self.assertEqual(mock.call_count, 1)
         self.assertFalse(observer.called)
diff --git a/test/test_main.py b/test/test_main.py
index 7c18495..3c9d5ec 100644
--- a/test/test_main.py
+++ b/test/test_main.py
@@ -586,11 +586,13 @@ class _TestMain(abc.ABC):
              'departing_unit': 'remote/42'},
         ), (
             EventSpec(ops.ActionEvent, 'start_action',
-                      env_var='JUJU_ACTION_NAME'),
+                      env_var='JUJU_ACTION_NAME',
+                      set_in_env={'JUJU_ACTION_UUID': '1'}),
             {},
         ), (
             EventSpec(ops.ActionEvent, 'foo_bar_action',
-                      env_var='JUJU_ACTION_NAME'),
+                      env_var='JUJU_ACTION_NAME',
+                      set_in_env={'JUJU_ACTION_UUID': '2'}),
             {},
         ), (
             EventSpec(ops.PebbleReadyEvent, 'test_pebble_ready',
@@ -726,19 +728,20 @@ class _TestMain(abc.ABC):
         fake_script(typing.cast(unittest.TestCase, self), 'action-get', "echo '{}'")
 
         test_cases = [(
-            EventSpec(ops.ActionEvent, 'log_critical_action', env_var='JUJU_ACTION_NAME'),
+            EventSpec(ops.ActionEvent, 'log_critical_action', env_var='JUJU_ACTION_NAME',
+                      set_in_env={'JUJU_ACTION_UUID': '1'}),
             ['juju-log', '--log-level', 'CRITICAL', '--', 'super critical'],
         ), (
             EventSpec(ops.ActionEvent, 'log_error_action',
-                      env_var='JUJU_ACTION_NAME'),
+                      env_var='JUJU_ACTION_NAME', set_in_env={'JUJU_ACTION_UUID': '2'}),
             ['juju-log', '--log-level', 'ERROR', '--', 'grave error'],
         ), (
             EventSpec(ops.ActionEvent, 'log_warning_action',
-                      env_var='JUJU_ACTION_NAME'),
+                      env_var='JUJU_ACTION_NAME', set_in_env={'JUJU_ACTION_UUID': '3'}),
             ['juju-log', '--log-level', 'WARNING', '--', 'wise warning'],
         ), (
             EventSpec(ops.ActionEvent, 'log_info_action',
-                      env_var='JUJU_ACTION_NAME'),
+                      env_var='JUJU_ACTION_NAME', set_in_env={'JUJU_ACTION_UUID': '4'}),
             ['juju-log', '--log-level', 'INFO', '--', 'useful info'],
         )]
 
@@ -779,7 +782,8 @@ class _TestMain(abc.ABC):
         state = self._simulate_event(EventSpec(
             ops.ActionEvent, 'get_model_name_action',
             env_var='JUJU_ACTION_NAME',
-            model_name='test-model-name'))
+            model_name='test-model-name',
+            set_in_env={'JUJU_ACTION_UUID': '1'}))
         assert isinstance(state, ops.BoundStoredState)
         self.assertEqual(state._on_get_model_name_action, ['test-model-name'])
 
@@ -791,7 +795,8 @@ class _TestMain(abc.ABC):
                     """echo '{"status": "unknown", "message": ""}'""")
         state = self._simulate_event(EventSpec(
             ops.ActionEvent, 'get_status_action',
-            env_var='JUJU_ACTION_NAME'))
+            env_var='JUJU_ACTION_NAME',
+            set_in_env={'JUJU_ACTION_UUID': '1'}))
         assert isinstance(state, ops.BoundStoredState)
         self.assertEqual(state.status_name, 'unknown')
         self.assertEqual(state.status_message, '')
@@ -801,7 +806,8 @@ class _TestMain(abc.ABC):
             """echo '{"status": "blocked", "message": "help meeee"}'""")
         state = self._simulate_event(EventSpec(
             ops.ActionEvent, 'get_status_action',
-            env_var='JUJU_ACTION_NAME'))
+            env_var='JUJU_ACTION_NAME',
+            set_in_env={'JUJU_ACTION_UUID': '1'}))
         assert isinstance(state, ops.BoundStoredState)
         self.assertEqual(state.status_name, 'blocked')
         self.assertEqual(state.status_message, 'help meeee')
@@ -1169,7 +1175,8 @@ class TestMainWithDispatch(_TestMainWithDispatch, unittest.TestCase):
         with self.assertRaises(subprocess.CalledProcessError):
             self._simulate_event(EventSpec(
                 ops.ActionEvent, 'keyerror_action',
-                env_var='JUJU_ACTION_NAME'))
+                env_var='JUJU_ACTION_NAME',
+                set_in_env={'JUJU_ACTION_UUID': '1'}))
         self.stderr.seek(0)
         stderr = self.stderr.read()
         self.assertIn('KeyError', stderr)
diff --git a/test/test_testing.py b/test/test_testing.py
index e937265..a2d0cfd 100644
--- a/test/test_testing.py
+++ b/test/test_testing.py
@@ -5445,6 +5445,7 @@ class TestActions(unittest.TestCase):
             def _on_simple_action(self, event: ops.ActionEvent):
                 """An action that doesn't generate logs, have any results, or fail."""
                 self.simple_was_called = True
+                assert isinstance(event.id, str)
 
             def _on_fail_action(self, event: ops.ActionEvent):
                 event.fail("this will be ignored")

expose JUJU_ACTION_UUID envvar
Some old hooks-based charms use the JUJU_ACTION_UUID envvar.
When porting them to use ops, it would be nice if there was an opsy way to access its value.
How about adding an `ActionEvent.uuid -> int` property to fetch it?
 

test/test_charm.py::TestCharm::test_action_event_defer_fails test/test_charm.py::TestCharm::test_action_events test/test_charm.py::TestCharm::test_invalid_action_results test/test_framework.py::DebugHookTests::test_actions_are_interrupted test/test_framework.py::DebugHookTests::test_interruption_enabled_with_all test/test_testing.py::TestActions::test_additional_params test/test_testing.py::TestActions::test_bad_results test/test_testing.py::TestActions::test_fail_action test/test_testing.py::TestActions::test_logs_and_results test/test_testing.py::TestActions::test_required_param test/test_testing.py::TestActions::test_run_action
test/test_charm.py::TestCharm::test_add_status_type_error test/test_charm.py::TestCharm::test_basic test/test_charm.py::TestCharm::test_collect_app_and_unit_status test/test_charm.py::TestCharm::test_collect_app_status_leader test/test_charm.py::TestCharm::test_collect_app_status_no_statuses test/test_charm.py::TestCharm::test_collect_app_status_non_leader test/test_charm.py::TestCharm::test_collect_status_priority test/test_charm.py::TestCharm::test_collect_unit_status test/test_charm.py::TestCharm::test_collect_unit_status_no_statuses test/test_charm.py::TestCharm::test_containers test/test_charm.py::TestCharm::test_containers_storage test/test_charm.py::TestCharm::test_containers_storage_multiple_mounts test/test_charm.py::TestCharm::test_empty_action test/test_charm.py::TestCharm::test_helper_properties test/test_charm.py::TestCharm::test_observe_decorated_method test/test_charm.py::TestCharm::test_relation_events test/test_charm.py::TestCharm::test_relations_meta test/test_charm.py::TestCharm::test_relations_meta_limit_type_validation test/test_charm.py::TestCharm::test_relations_meta_scope_type_validation test/test_charm.py::TestCharm::test_secret_events test/test_charm.py::TestCharm::test_storage_events test/test_charm.py::TestCharm::test_workload_events test/test_charm.py::TestCharmMeta::test_assumes test/test_charm.py::TestCharmMeta::test_links test/test_charm.py::TestCharmMeta::test_links_charmcraft_yaml test/test_framework.py::TestFramework::test_auto_register_event_types test/test_framework.py::TestFramework::test_bad_sig_observer test/test_framework.py::TestFramework::test_ban_concurrent_frameworks test/test_framework.py::TestFramework::test_conflicting_event_attributes test/test_framework.py::TestFramework::test_custom_event_data test/test_framework.py::TestFramework::test_defer_and_reemit test/test_framework.py::TestFramework::test_deprecated_init test/test_framework.py::TestFramework::test_dynamic_event_types test/test_framework.py::TestFramework::test_event_key_roundtrip test/test_framework.py::TestFramework::test_event_regex test/test_framework.py::TestFramework::test_events_base test/test_framework.py::TestFramework::test_forget_and_multiple_objects test/test_framework.py::TestFramework::test_forget_and_multiple_objects_with_load_snapshot test/test_framework.py::TestFramework::test_handle_attrs_readonly test/test_framework.py::TestFramework::test_handle_path test/test_framework.py::TestFramework::test_helper_properties test/test_framework.py::TestFramework::test_on_pre_commit_emitted test/test_framework.py::TestFramework::test_reemit_ignores_unknown_event_type test/test_framework.py::TestFramework::test_remove_unreferenced_events test/test_framework.py::TestFramework::test_restore_unknown test/test_framework.py::TestFramework::test_simple_event_observer test/test_framework.py::TestFramework::test_snapshot_roundtrip test/test_framework.py::TestFramework::test_snapshot_saving_restricted_to_simple_types test/test_framework.py::TestFramework::test_unobserved_events_dont_leave_cruft test/test_framework.py::TestFramework::test_weak_observer test/test_framework.py::TestStoredState::test_basic_state_storage test/test_framework.py::TestStoredState::test_comparison_operations test/test_framework.py::TestStoredState::test_mutable_types test/test_framework.py::TestStoredState::test_mutable_types_invalid test/test_framework.py::TestStoredState::test_same_name_two_classes test/test_framework.py::TestStoredState::test_set_default test/test_framework.py::TestStoredState::test_set_operations test/test_framework.py::TestStoredState::test_stored_dict_repr test/test_framework.py::TestStoredState::test_stored_list_repr test/test_framework.py::TestStoredState::test_stored_set_repr test/test_framework.py::TestStoredState::test_straight_sub_subclass test/test_framework.py::TestStoredState::test_straight_subclass test/test_framework.py::TestStoredState::test_the_crazy_thing test/test_framework.py::TestStoredState::test_two_names_one_state test/test_framework.py::TestStoredState::test_two_subclasses test/test_framework.py::TestStoredState::test_two_subclasses_no_conflicts test/test_framework.py::BreakpointTests::test_breakpoint_builtin_sanity test/test_framework.py::BreakpointTests::test_breakpoint_builtin_unset test/test_framework.py::BreakpointTests::test_breakpoint_names test/test_framework.py::BreakpointTests::test_builtin_breakpoint_hooked test/test_framework.py::BreakpointTests::test_ignored test/test_framework.py::BreakpointTests::test_named_indicated_all test/test_framework.py::BreakpointTests::test_named_indicated_hook test/test_framework.py::BreakpointTests::test_named_indicated_ingroup test/test_framework.py::BreakpointTests::test_named_indicated_somethingelse test/test_framework.py::BreakpointTests::test_named_indicated_specifically test/test_framework.py::BreakpointTests::test_named_indicated_unnamed test/test_framework.py::BreakpointTests::test_pdb_properly_called test/test_framework.py::BreakpointTests::test_unnamed_indicated_all test/test_framework.py::BreakpointTests::test_unnamed_indicated_hook test/test_framework.py::BreakpointTests::test_welcome_message test/test_framework.py::BreakpointTests::test_welcome_message_not_multiple test/test_framework.py::DebugHookTests::test_basic_interruption_enabled test/test_framework.py::DebugHookTests::test_envvar_missing test/test_framework.py::DebugHookTests::test_envvar_mixed test/test_framework.py::DebugHookTests::test_envvar_nohook test/test_framework.py::DebugHookTests::test_envvar_parsing_empty test/test_framework.py::DebugHookTests::test_envvar_parsing_missing test/test_framework.py::DebugHookTests::test_envvar_parsing_multiple test/test_framework.py::DebugHookTests::test_envvar_parsing_simple test/test_framework.py::DebugHookTests::test_internal_events_not_interrupted test/test_framework.py::DebugHookTests::test_no_registered_method test/test_framework.py::DebugHookTests::test_welcome_message_not_multiple test/test_main.py::CharmInitTestCase::test_breakpoint test/test_main.py::CharmInitTestCase::test_controller_storage_deprecated test/test_main.py::CharmInitTestCase::test_init_signature_old_key_argument test/test_main.py::CharmInitTestCase::test_init_signature_only_framework test/test_main.py::CharmInitTestCase::test_init_signature_passthrough test/test_main.py::CharmInitTestCase::test_no_debug_breakpoint test/test_main.py::CharmInitTestCase::test_storage_no_storage test/test_main.py::CharmInitTestCase::test_storage_with_storage test/test_main.py::TestDispatch::test_most_legacy test/test_main.py::TestDispatch::test_with_dispatch test/test_main.py::TestDispatch::test_with_dispatch_path_but_no_dispatch test/test_main.py::TestMainWithNoDispatch::test_collect_metrics test/test_main.py::TestMainWithNoDispatch::test_custom_event test/test_main.py::TestMainWithNoDispatch::test_empty_actions test/test_main.py::TestMainWithNoDispatch::test_event_not_implemented test/test_main.py::TestMainWithNoDispatch::test_event_reemitted test/test_main.py::TestMainWithNoDispatch::test_excepthook test/test_main.py::TestMainWithNoDispatch::test_has_valid_status test/test_main.py::TestMainWithNoDispatch::test_logger test/test_main.py::TestMainWithNoDispatch::test_multiple_events_handled test/test_main.py::TestMainWithNoDispatch::test_no_actions test/test_main.py::TestMainWithNoDispatch::test_no_reemission_on_collect_metrics test/test_main.py::TestMainWithNoDispatch::test_sets_model_name test/test_main.py::TestMainWithNoDispatch::test_setup_action_links test/test_main.py::TestMainWithNoDispatch::test_setup_event_links test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_collect_metrics test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_custom_event test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_empty_actions test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_event_not_implemented test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_event_reemitted test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_excepthook test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_has_valid_status test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_logger test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_multiple_events_handled test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_no_actions test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_no_reemission_on_collect_metrics test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_sets_model_name test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_setup_action_links test/test_main.py::TestMainWithNoDispatchButJujuIsDispatchAware::test_setup_event_links test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_collect_metrics test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_custom_event test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_empty_actions test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_event_not_implemented test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_event_reemitted test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_excepthook test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_has_valid_status test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_logger test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_multiple_events_handled test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_no_actions test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_no_reemission_on_collect_metrics test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_sets_model_name test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_setup_action_links test/test_main.py::TestMainWithNoDispatchButDispatchPathIsSet::test_setup_event_links test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_collect_metrics test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_custom_event test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_empty_actions test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_event_not_implemented test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_event_reemitted test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_excepthook test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_has_valid_status test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_logger test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_multiple_events_handled test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_no_actions test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_no_reemission_on_collect_metrics test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_sets_model_name test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_setup_action_links test/test_main.py::TestMainWithNoDispatchButScriptsAreCopies::test_setup_event_links test/test_main.py::TestMainWithDispatch::test_collect_metrics test/test_main.py::TestMainWithDispatch::test_crash_action test/test_main.py::TestMainWithDispatch::test_custom_event test/test_main.py::TestMainWithDispatch::test_empty_actions test/test_main.py::TestMainWithDispatch::test_event_not_implemented test/test_main.py::TestMainWithDispatch::test_event_reemitted test/test_main.py::TestMainWithDispatch::test_excepthook test/test_main.py::TestMainWithDispatch::test_has_valid_status test/test_main.py::TestMainWithDispatch::test_hook_and_dispatch test/test_main.py::TestMainWithDispatch::test_hook_and_dispatch_but_hook_is_dispatch test/test_main.py::TestMainWithDispatch::test_hook_and_dispatch_but_hook_is_dispatch_copy test/test_main.py::TestMainWithDispatch::test_hook_and_dispatch_with_failing_hook test/test_main.py::TestMainWithDispatch::test_logger test/test_main.py::TestMainWithDispatch::test_multiple_events_handled test/test_main.py::TestMainWithDispatch::test_no_actions test/test_main.py::TestMainWithDispatch::test_no_reemission_on_collect_metrics test/test_main.py::TestMainWithDispatch::test_non_executable_hook_and_dispatch test/test_main.py::TestMainWithDispatch::test_sets_model_name test/test_main.py::TestMainWithDispatch::test_setup_event_links test/test_main.py::TestMainWithDispatchAsScript::test_collect_metrics test/test_main.py::TestMainWithDispatchAsScript::test_custom_event test/test_main.py::TestMainWithDispatchAsScript::test_empty_actions test/test_main.py::TestMainWithDispatchAsScript::test_event_not_implemented test/test_main.py::TestMainWithDispatchAsScript::test_event_reemitted test/test_main.py::TestMainWithDispatchAsScript::test_excepthook test/test_main.py::TestMainWithDispatchAsScript::test_has_valid_status test/test_main.py::TestMainWithDispatchAsScript::test_hook_and_dispatch test/test_main.py::TestMainWithDispatchAsScript::test_hook_and_dispatch_but_hook_is_dispatch test/test_main.py::TestMainWithDispatchAsScript::test_hook_and_dispatch_but_hook_is_dispatch_copy test/test_main.py::TestMainWithDispatchAsScript::test_hook_and_dispatch_with_failing_hook test/test_main.py::TestMainWithDispatchAsScript::test_logger test/test_main.py::TestMainWithDispatchAsScript::test_multiple_events_handled test/test_main.py::TestMainWithDispatchAsScript::test_no_actions test/test_main.py::TestMainWithDispatchAsScript::test_no_reemission_on_collect_metrics test/test_main.py::TestMainWithDispatchAsScript::test_non_executable_hook_and_dispatch test/test_main.py::TestMainWithDispatchAsScript::test_sets_model_name test/test_main.py::TestMainWithDispatchAsScript::test_setup_event_links test/test_main.py::TestStorageHeuristics::test_fallback_to_current_juju_version__new_enough test/test_main.py::TestStorageHeuristics::test_fallback_to_current_juju_version__too_old test/test_main.py::TestStorageHeuristics::test_not_if_already_local test/test_main.py::TestStorageHeuristics::test_not_if_not_in_k8s test/test_testing.py::TestHarness::test_actions_from_directory test/test_testing.py::TestHarness::test_actions_from_directory_charmcraft_yaml test/test_testing.py::TestHarness::test_actions_passed_in test/test_testing.py::TestHarness::test_add_layer_with_log_targets_to_plan test/test_testing.py::TestHarness::test_add_oci_resource_custom test/test_testing.py::TestHarness::test_add_oci_resource_no_image test/test_testing.py::TestHarness::test_add_peer_relation_with_initial_data_leader test/test_testing.py::TestHarness::test_add_relation test/test_testing.py::TestHarness::test_add_relation_and_unit test/test_testing.py::TestHarness::test_add_relation_with_app_data test/test_testing.py::TestHarness::test_add_relation_with_our_initial_data test/test_testing.py::TestHarness::test_add_relation_with_remote_app_data test/test_testing.py::TestHarness::test_add_relation_with_unit_data test/test_testing.py::TestHarness::test_add_resource_but_oci test/test_testing.py::TestHarness::test_add_resource_bytes test/test_testing.py::TestHarness::test_add_resource_string test/test_testing.py::TestHarness::test_add_resource_unknown test/test_testing.py::TestHarness::test_add_resource_unknown_filename test/test_testing.py::TestHarness::test_add_storage_after_harness_begin test/test_testing.py::TestHarness::test_add_storage_before_harness_begin test/test_testing.py::TestHarness::test_add_storage_not_attached_default test/test_testing.py::TestHarness::test_add_storage_then_harness_begin test/test_testing.py::TestHarness::test_add_storage_without_metadata_key_fails test/test_testing.py::TestHarness::test_app_status test/test_testing.py::TestHarness::test_attach_storage test/test_testing.py::TestHarness::test_attach_storage_before_harness_begin test/test_testing.py::TestHarness::test_bad_config_option_type test/test_testing.py::TestHarness::test_begin_twice test/test_testing.py::TestHarness::test_begin_with_initial_hooks_install_sets_status test/test_testing.py::TestHarness::test_begin_with_initial_hooks_multiple_relation_same_endpoint test/test_testing.py::TestHarness::test_begin_with_initial_hooks_no_relations test/test_testing.py::TestHarness::test_begin_with_initial_hooks_no_relations_not_leader test/test_testing.py::TestHarness::test_begin_with_initial_hooks_peer_relation_pre_defined test/test_testing.py::TestHarness::test_begin_with_initial_hooks_relation_charm_with_no_relation test/test_testing.py::TestHarness::test_begin_with_initial_hooks_unknown_status test/test_testing.py::TestHarness::test_begin_with_initial_hooks_with_application_data test/test_testing.py::TestHarness::test_begin_with_initial_hooks_with_multiple_units test/test_testing.py::TestHarness::test_begin_with_initial_hooks_with_one_relation test/test_testing.py::TestHarness::test_begin_with_initial_hooks_with_peer_relation test/test_testing.py::TestHarness::test_can_connect_begin_with_initial_hooks test/test_testing.py::TestHarness::test_can_connect_default test/test_testing.py::TestHarness::test_config_from_directory test/test_testing.py::TestHarness::test_config_from_directory_charmcraft_yaml test/test_testing.py::TestHarness::test_container_isdir_and_exists test/test_testing.py::TestHarness::test_container_pebble_ready test/test_testing.py::TestHarness::test_create_harness_twice test/test_testing.py::TestHarness::test_detach_storage test/test_testing.py::TestHarness::test_detach_storage_before_harness_begin test/test_testing.py::TestHarness::test_empty_config_raises test/test_testing.py::TestHarness::test_evaluate_status test/test_testing.py::TestHarness::test_event_context test/test_testing.py::TestHarness::test_event_context_inverse test/test_testing.py::TestHarness::test_get_backend_calls test/test_testing.py::TestHarness::test_get_backend_calls_with_kwargs test/test_testing.py::TestHarness::test_get_filesystem_root test/test_testing.py::TestHarness::test_get_pebble_container_plan test/test_testing.py::TestHarness::test_get_pebble_container_plan_unknown test/test_testing.py::TestHarness::test_get_pod_spec test/test_testing.py::TestHarness::test_get_relation_data test/test_testing.py::TestHarness::test_harness_leader_misconfig test/test_testing.py::TestHarness::test_hooks_disabled_contextmanager test/test_testing.py::TestHarness::test_hooks_disabled_nested_contextmanager test/test_testing.py::TestHarness::test_hooks_disabled_noop test/test_testing.py::TestHarness::test_hooks_enabled_and_disabled test/test_testing.py::TestHarness::test_invalid_status_set test/test_testing.py::TestHarness::test_metadata_from_directory test/test_testing.py::TestHarness::test_metadata_from_directory_charmcraft_yaml test/test_testing.py::TestHarness::test_no_config_option_type test/test_testing.py::TestHarness::test_no_event_on_empty_update_relation_unit_app test/test_testing.py::TestHarness::test_no_event_on_empty_update_relation_unit_bag test/test_testing.py::TestHarness::test_no_event_on_no_diff_update_relation_unit_app test/test_testing.py::TestHarness::test_no_event_on_no_diff_update_relation_unit_bag test/test_testing.py::TestHarness::test_populate_oci_resources test/test_testing.py::TestHarness::test_relation_events test/test_testing.py::TestHarness::test_relation_get_when_broken test/test_testing.py::TestHarness::test_relation_set_app_not_leader test/test_testing.py::TestHarness::test_relation_set_deletes test/test_testing.py::TestHarness::test_relation_set_nonstring test/test_testing.py::TestHarness::test_remove_detached_storage test/test_testing.py::TestHarness::test_remove_relation test/test_testing.py::TestHarness::test_remove_relation_marks_relation_as_inactive test/test_testing.py::TestHarness::test_remove_relation_unit test/test_testing.py::TestHarness::test_remove_specific_relation_id test/test_testing.py::TestHarness::test_remove_storage_after_harness_begin test/test_testing.py::TestHarness::test_remove_storage_before_harness_begin test/test_testing.py::TestHarness::test_remove_storage_without_metadata_key_fails test/test_testing.py::TestHarness::test_removing_invalid_relation_id_raises_exception test/test_testing.py::TestHarness::test_removing_relation_refreshes_charm_model test/test_testing.py::TestHarness::test_removing_relation_removes_remote_app_data test/test_testing.py::TestHarness::test_removing_relation_unit_does_not_remove_other_unit_and_data test/test_testing.py::TestHarness::test_removing_relation_unit_removes_data_also test/test_testing.py::TestHarness::test_resource_folder_cleanup test/test_testing.py::TestHarness::test_set_leader test/test_testing.py::TestHarness::test_set_model_info_after_begin test/test_testing.py::TestHarness::test_set_model_name test/test_testing.py::TestHarness::test_set_model_name_after_begin test/test_testing.py::TestHarness::test_set_model_uuid_after_begin test/test_testing.py::TestHarness::test_set_workload_version test/test_testing.py::TestHarness::test_storage_with_hyphens_works test/test_testing.py::TestHarness::test_uncastable_config_option_type test/test_testing.py::TestHarness::test_unit_status test/test_testing.py::TestHarness::test_update_config test/test_testing.py::TestHarness::test_update_config_bad_type test/test_testing.py::TestHarness::test_update_config_undefined_option test/test_testing.py::TestHarness::test_update_config_unset_boolean test/test_testing.py::TestHarness::test_update_peer_relation_app_data test/test_testing.py::TestHarness::test_update_peer_relation_no_local_unit_change_event test/test_testing.py::TestHarness::test_update_relation_exposes_new_data test/test_testing.py::TestHarness::test_update_relation_no_local_app_change_event test/test_testing.py::TestHarness::test_update_relation_no_local_unit_change_event test/test_testing.py::TestHarness::test_update_relation_remove_data test/test_testing.py::TestNetwork::test_add_network_all_args test/test_testing.py::TestNetwork::test_add_network_default_fallback test/test_testing.py::TestNetwork::test_add_network_defaults test/test_testing.py::TestNetwork::test_add_network_endpoint_and_relation_id_do_not_correspond test/test_testing.py::TestNetwork::test_add_network_endpoint_fallback test/test_testing.py::TestNetwork::test_add_network_endpoint_not_in_meta test/test_testing.py::TestNetwork::test_add_network_ipv6 test/test_testing.py::TestNetwork::test_add_network_relation_id_incorrect test/test_testing.py::TestNetwork::test_add_network_relation_id_set_endpoint_not_set test/test_testing.py::TestNetwork::test_add_network_specific_endpoint test/test_testing.py::TestNetwork::test_add_network_specific_relation test/test_testing.py::TestNetwork::test_network_get_relation_not_found test/test_testing.py::TestTestingModelBackend::test_conforms_to_model_backend test/test_testing.py::TestTestingModelBackend::test_get_pebble_methods test/test_testing.py::TestTestingModelBackend::test_lazy_resource_directory test/test_testing.py::TestTestingModelBackend::test_model_uuid_is_uuid_v4 test/test_testing.py::TestTestingModelBackend::test_reboot test/test_testing.py::TestTestingModelBackend::test_relation_get_unknown_relation_id test/test_testing.py::TestTestingModelBackend::test_relation_ids_unknown_relation test/test_testing.py::TestTestingModelBackend::test_relation_list_unknown_relation_id test/test_testing.py::TestTestingModelBackend::test_relation_remote_app_name test/test_testing.py::TestTestingModelBackend::test_resource_get_no_resource test/test_testing.py::TestTestingModelBackend::test_status_set_get_app test/test_testing.py::TestTestingModelBackend::test_status_set_get_unit test/test_testing.py::TestTestingPebbleClient::test_add_layer test/test_testing.py::TestTestingPebbleClient::test_add_layer_combine_no_override test/test_testing.py::TestTestingPebbleClient::test_add_layer_combine_override_merge test/test_testing.py::TestTestingPebbleClient::test_add_layer_combine_override_replace test/test_testing.py::TestTestingPebbleClient::test_add_layer_combine_override_unknown test/test_testing.py::TestTestingPebbleClient::test_add_layer_merge test/test_testing.py::TestTestingPebbleClient::test_add_layer_not_combined test/test_testing.py::TestTestingPebbleClient::test_add_layer_three_services test/test_testing.py::TestTestingPebbleClient::test_get_services_autostart test/test_testing.py::TestTestingPebbleClient::test_get_services_bad_request test/test_testing.py::TestTestingPebbleClient::test_get_services_none test/test_testing.py::TestTestingPebbleClient::test_get_services_not_started test/test_testing.py::TestTestingPebbleClient::test_get_services_start_stop test/test_testing.py::TestTestingPebbleClient::test_get_services_subset test/test_testing.py::TestTestingPebbleClient::test_get_services_unknown test/test_testing.py::TestTestingPebbleClient::test_invalid_start_service test/test_testing.py::TestTestingPebbleClient::test_methods_match_pebble_client test/test_testing.py::TestTestingPebbleClient::test_mixed_start_service test/test_testing.py::TestTestingPebbleClient::test_send_signal test/test_testing.py::TestTestingPebbleClient::test_start_service_str test/test_testing.py::TestTestingPebbleClient::test_start_started_service test/test_testing.py::TestTestingPebbleClient::test_stop_service_str test/test_testing.py::TestTestingPebbleClient::test_stop_services_unknown test/test_testing.py::TestTestingPebbleClient::test_stop_stopped_service test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_container_storage_mounts test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_list_directory_object_itself test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_list_files_not_found_raises test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_list_files_unnamed test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_make_dir_with_ownership test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_make_dir_with_permission_mask test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_make_directory test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_make_directory_recursively test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_make_directory_with_relative_path_fails test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_make_subdir_of_file_fails test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_pull_directory test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_pull_not_found test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_and_list_file test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_and_pull_bytes test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_and_pull_larger_file test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_and_pull_non_utf8_data test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_as_child_of_file_raises_error test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_bytes_ignore_encoding test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_bytesio_ignore_encoding test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_file_with_relative_path_fails test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_files_and_list test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_files_and_list_by_pattern test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_to_non_existent_subdir test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_with_ownership test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_push_with_permission_mask test/test_testing.py::TestPebbleStorageAPIsUsingMocks::test_remove_path test/test_testing.py::TestFilesystem::test_list_files test/test_testing.py::TestFilesystem::test_make_dir test/test_testing.py::TestFilesystem::test_pull test/test_testing.py::TestFilesystem::test_pull_path test/test_testing.py::TestFilesystem::test_push test/test_testing.py::TestFilesystem::test_push_create_parent test/test_testing.py::TestFilesystem::test_push_path test/test_testing.py::TestFilesystem::test_storage_mount test/test_testing.py::TestSecrets::test_add_model_secret_by_app_instance test/test_testing.py::TestSecrets::test_add_model_secret_by_app_name_str test/test_testing.py::TestSecrets::test_add_model_secret_by_unit_instance test/test_testing.py::TestSecrets::test_add_model_secret_invalid_content test/test_testing.py::TestSecrets::test_get_secret_and_refresh test/test_testing.py::TestSecrets::test_get_secret_as_owner test/test_testing.py::TestSecrets::test_get_secret_by_label test/test_testing.py::TestSecrets::test_get_secret_grants test/test_testing.py::TestSecrets::test_get_secret_removed test/test_testing.py::TestSecrets::test_grant_secret_and_revoke_secret test/test_testing.py::TestSecrets::test_grant_secret_no_relation test/test_testing.py::TestSecrets::test_grant_secret_wrong_app test/test_testing.py::TestSecrets::test_grant_secret_wrong_unit test/test_testing.py::TestSecrets::test_secret_permissions_leader test/test_testing.py::TestSecrets::test_secret_permissions_nonleader test/test_testing.py::TestSecrets::test_secret_permissions_unit test/test_testing.py::TestSecrets::test_set_secret_content test/test_testing.py::TestSecrets::test_set_secret_content_invalid_content test/test_testing.py::TestSecrets::test_set_secret_content_invalid_secret_id test/test_testing.py::TestSecrets::test_set_secret_content_wrong_owner test/test_testing.py::TestSecrets::test_trigger_secret_expiration test/test_testing.py::TestSecrets::test_trigger_secret_removal test/test_testing.py::TestSecrets::test_trigger_secret_rotation test/test_testing.py::TestPorts::test_errors test/test_testing.py::TestPorts::test_ports test/test_testing.py::TestHandleExec::test_combined_error test/test_testing.py::TestHandleExec::test_exec_service_context test/test_testing.py::TestHandleExec::test_exec_stdin test/test_testing.py::TestHandleExec::test_exec_stdout_stderr test/test_testing.py::TestHandleExec::test_exec_timeout test/test_testing.py::TestHandleExec::test_re_register_handler test/test_testing.py::TestHandleExec::test_register_handler test/test_testing.py::TestHandleExec::test_register_match_all_prefix test/test_testing.py::TestHandleExec::test_register_with_handler test/test_testing.py::TestHandleExec::test_register_with_result test/test_testing.py::TestActions::test_before_begin test/test_testing.py::TestActions::test_invalid_action test/test_testing.py::TestNotify::test_notify_basics test/test_testing.py::TestNotify::test_notify_no_begin test/test_testing.py::TestNotify::test_notify_no_repeat test/test_testing.py::TestNotices::test_get_notice_by_id test/test_testing.py::TestNotices::test_get_notices