diff --git a/ops/charm.py b/ops/charm.py
index 59ad4c5..eae71a2 100644
--- a/ops/charm.py
+++ b/ops/charm.py
@@ -1540,7 +1540,9 @@ class StorageMeta:
         self.multiple_range = None
         if 'multiple' in raw:
             range = raw['multiple']['range']
-            if '-' not in range:
+            if range[-1] == '+':
+                self.multiple_range = (int(range[:-1]), None)
+            elif '-' not in range:
                 self.multiple_range = (int(range), int(range))
             else:
                 range = range.split('-')

diff --git a/ops/testing.py b/ops/testing.py
index e3c9556..cb395be 100644
--- a/ops/testing.py
+++ b/ops/testing.py
@@ -405,7 +405,11 @@ class Harness(Generic[CharmType]):
         for storage_name in self._meta.storages:
             for storage_index in self._backend.storage_list(storage_name, include_detached=True):
                 s = model.Storage(storage_name, storage_index, self._backend)
-                self.attach_storage(s.full_id)
+                if self._backend._storage_is_attached(storage_name, storage_index):
+                    # Attaching was done already, but we still need the event to be emitted.
+                    self.charm.on[storage_name].storage_attached.emit(s)
+                else:
+                    self.attach_storage(s.full_id)
         # Storage done, emit install event
         charm.on.install.emit()
 
@@ -690,8 +694,8 @@ class Harness(Generic[CharmType]):
         Args:
             storage_name: The storage backend name on the Charm
             count: Number of disks being added
-            attach: True to also attach the storage mount and emit storage-attached if
-                harness.begin() has been called.
+            attach: True to also attach the storage mount; if :meth:`begin`
+                has been called a True value will also emit storage-attached
 
         Return:
             A list of storage IDs, e.g. ["my-storage/1", "my-storage/2"].
@@ -739,12 +743,12 @@ class Harness(Generic[CharmType]):
         """Attach a storage device.
 
         The intent of this function is to simulate a ``juju attach-storage`` call.
-        It will trigger a storage-attached hook if the storage unit in question exists
+        If called after :meth:`begin` and hooks are not disabled, it will trigger
+        a storage-attached hook if the storage unit in question exists
         and is presently marked as detached.
 
         The test harness uses symbolic links to imitate storage mounts, which may lead to some
-        inconsistencies compared to the actual charm. Users should be cognizant of
-        this potential discrepancy.
+        inconsistencies compared to the actual charm.
 
         Args:
             storage_id: The full storage ID of the storage unit being attached, including the
@@ -2339,7 +2343,17 @@ class _TestingModelBackend:
                     mounting_dir.parent.mkdir(parents=True, exist_ok=True)
                     target_dir = pathlib.Path(store["location"])
                     target_dir.mkdir(parents=True, exist_ok=True)
-                    mounting_dir.symlink_to(target_dir)
+                    try:
+                        mounting_dir.symlink_to(target_dir, target_is_directory=True)
+                    except FileExistsError:
+                        # If the symlink is already the one we want, then we
+                        # don't need to do anything here.
+                        # NOTE: In Python 3.9, this can use `mounting_dir.readlink()`
+                        if (
+                            not mounting_dir.is_symlink()
+                            or os.readlink(mounting_dir) != str(target_dir)
+                        ):
+                            raise
 
         index = int(index)
         if not self._storage_is_attached(name, index):
diff --git a/test/test_charm.py b/test/test_charm.py
index 1a9ac52..9767588 100644
--- a/test/test_charm.py
+++ b/test/test_charm.py
@@ -282,6 +282,10 @@ storage:
     multiple:
       range: 2-
     type: filesystem
+  stor-plus:
+    multiple:
+      range: 10+
+    type: filesystem
 ''')
 
         fake_script(
@@ -329,6 +333,7 @@ storage:
         self.assertEqual(self.meta.storages['stor2'].multiple_range, (2, 2))
         self.assertEqual(self.meta.storages['stor3'].multiple_range, (2, None))
         self.assertEqual(self.meta.storages['stor-4'].multiple_range, (2, 4))
+        self.assertEqual(self.meta.storages['stor-plus'].multiple_range, (10, None))
 
         charm = MyCharm(self.create_framework())
 
diff --git a/test/test_testing.py b/test/test_testing.py
index fcb3369..d531e08 100644
--- a/test/test_testing.py
+++ b/test/test_testing.py
@@ -4749,6 +4749,96 @@ class TestFilesystem(unittest.TestCase, _TestingPebbleClientMixin):
         self.harness.attach_storage(storage_id)
         self.assertTrue((self.root / "mounts/foo/bar").read_text(), "foobar")
 
+    def _make_storage_attach_harness(self, meta: typing.Optional[str] = None):
+        class MyCharm(ops.CharmBase):
+            def __init__(self, framework: ops.Framework):
+                super().__init__(framework)
+                self.attached: typing.List[str] = []
+                self.locations: typing.List[pathlib.Path] = []
+                framework.observe(self.on['test-storage'].storage_attached, self._on_attach)
+
+            def _on_attach(self, event: ops.StorageAttachedEvent):
+                self.attached.append(event.storage.full_id)
+                self.locations.append(event.storage.location)
+
+        if meta is None:
+            meta = '''
+                name: test
+                containers:
+                    test-container:
+                        mounts:
+                            - storage: test-storage
+                              location: /mounts/foo
+                storage:
+                    test-storage:
+                        type: filesystem
+                '''
+        harness = ops.testing.Harness(MyCharm, meta=meta)
+        self.addCleanup(harness.cleanup)
+        return harness
+
+    def test_storage_attach_begin_no_emit(self):
+        """If `begin()` hasn't been called, `attach` does not emit storage-attached."""
+        harness = self._make_storage_attach_harness()
+        harness.add_storage('test-storage', attach=True)
+        harness.begin()
+        self.assertNotIn('test-storage/0', harness.charm.attached)
+
+    def test_storage_attach_begin_with_hooks_emits(self):
+        """`attach` doesn't emit storage-attached before `begin_with_initial_hooks`."""
+        harness = self._make_storage_attach_harness()
+        harness.add_storage('test-storage', attach=True)
+        harness.begin_with_initial_hooks()
+        self.assertIn('test-storage/0', harness.charm.attached)
+        self.assertTrue(harness.charm.locations[0])
+
+    def test_storage_add_with_later_attach(self):
+        harness = self._make_storage_attach_harness()
+        harness.begin()
+        storage_ids = harness.add_storage('test-storage', attach=False)
+        self.assertNotIn('test-storage/0', harness.charm.attached)
+        for s_id in storage_ids:
+            harness.attach_storage(s_id)
+            # It's safe to call `attach_storage` more than once, and this will
+            # only trigger the event once - this is the same as executing
+            # `juju attach-storage <unit> <storage>` more than once.
+            harness.attach_storage(s_id)
+        self.assertEqual(harness.charm.attached.count('test-storage/0'), 1)
+
+    def test_storage_machine_charm_metadata(self):
+        meta = '''
+            name: test
+            storage:
+                test-storage:
+                    type: filesystem
+                    mount: /mounts/foo
+            '''
+        harness = self._make_storage_attach_harness(meta)
+        harness.begin()
+        harness.add_storage('test-storage', attach=True)
+        self.assertIn('test-storage/0', harness.charm.attached)
+
+    def test_storage_multiple_storage_instances(self):
+        meta = '''
+            name: test
+            storage:
+                test-storage:
+                    type: filesystem
+                    mount: /mounts/foo
+                    multiple:
+                        range: 2-4
+            '''
+        harness = self._make_storage_attach_harness(meta)
+        harness.begin()
+        harness.add_storage('test-storage', 2, attach=True)
+        self.assertEqual(harness.charm.attached, ['test-storage/0', 'test-storage/1'])
+        self.assertNotEqual(harness.charm.locations[0], harness.charm.locations[1])
+        harness.add_storage('test-storage', 2, attach=True)
+        self.assertEqual(
+            harness.charm.attached, [
+                'test-storage/0', 'test-storage/1', 'test-storage/2', 'test-storage/3'])
+        self.assertEqual(len(set(harness.charm.locations)), 4)
+
 
 class TestSecrets(unittest.TestCase):
     def test_add_model_secret_by_app_name_str(self):

Using Harness.add_storage(..., attach=True) before begin_with_initial_hooks gives confusing error
For example:

```python
import ops
import ops.testing

class MyCharm(ops.CharmBase):
    pass

meta = """
            containers:
                test-container:
                    mounts:
                        - storage: test-storage
                          location: /mounts/foo
            storage:
                test-storage:
                    type: filesystem
"""

h = ops.testing.Harness(MyCharm, meta=meta)
try:
    h.add_storage("test-storage", attach=True)
    h.begin_with_initial_hooks()
finally:
    h.cleanup()
```

If `begin()` is used instead of `begin_with_initial_hooks`, everything is ok, because `begin_with_initial_hooks` does the attaching (to then emit the `StorageAttached` event) and `begin` does not. Similarly, if `attach=True` is not used, everything is ok.

However, when run as above, an error like this is generated:

```
Traceback (most recent call last):
  File "/tmp/storagetest/test.py", line 41, in <module>
    h.begin_with_initial_hooks()
  File "/tmp/storagetest/.venv/lib/python3.11/site-packages/ops/testing.py", line 407, in begin_with_initial_hooks
    self.attach_storage(s.full_id)
  File "/tmp/storagetest/.venv/lib/python3.11/site-packages/ops/testing.py", line 752, in attach_storage
    if not self._backend._storage_attach(storage_id):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/storagetest/.venv/lib/python3.11/site-packages/ops/testing.py", line 2348, in _storage_attach
    mounting_dir.symlink_to(target_dir)
  File "/usr/lib/python3.11/pathlib.py", line 1199, in symlink_to
    os.symlink(target, self, target_is_directory)
FileExistsError: [Errno 17] File exists: '/tmp/ops-harness-z2tflymi/storages/test-storage/0' -> '/tmp/ops-harness-z2tflymi/containers/test-container/mounts/foo'
```

This happens because the `add_storage` call mounts the storage, and then the `begin_with_initial_hooks` attempts to mount it again, but that's already happened, so the symlinking fails.

We should:

* [ ] Adjust the API documentation so that it explicitly says that `attach` does nothing if `begin` hasn't yet been called
* [ ] Verify that `begin` has been called (ie. `._charm is not None` before trying to mount the storage
test/test_charm.py::TestCharm::test_storage_events
test/test_charm.py::TestCharm::test_action_event_defer_fails test/test_charm.py::TestCharm::test_action_events 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_invalid_action_results test/test_charm.py::TestCharm::test_observe_decorated_method test/test_charm.py::TestCharm::test_observer_not_referenced_warning 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_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_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_no_meta_fails 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_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_add_relation_network_get 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_add_with_later_attach test/test_testing.py::TestFilesystem::test_storage_attach_begin_no_emit test/test_testing.py::TestFilesystem::test_storage_attach_begin_with_hooks_emits test/test_testing.py::TestFilesystem::test_storage_machine_charm_metadata test/test_testing.py::TestFilesystem::test_storage_mount test/test_testing.py::TestFilesystem::test_storage_multiple_storage_instances 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_additional_params test/test_testing.py::TestActions::test_bad_results test/test_testing.py::TestActions::test_before_begin test/test_testing.py::TestActions::test_fail_action test/test_testing.py::TestActions::test_invalid_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_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