test: type-check testing/src/scenario and fix uncovered errors#2615
test: type-check testing/src/scenario and fix uncovered errors#2615tonyandrewmeyer wants to merge 5 commits into
Conversation
Since ops 3.8.0, Secret.remote_grants values are frozensets, but secret_grant and secret_revoke still called .add() and .remove() on them, raising AttributeError when a charm revoked a pre-existing grant. Replace the in-place mutations with reassignment using set operators. Fixes canonical#2613 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The pyright \`include\` glob \`testing/src/*.py\` did not match the
subdirectory where the scenario source actually lives, so all of
testing/src/scenario/*.py was silently unchecked. Add the correct
glob and fix the errors that surfaced.
Notable fixes:
- mocking.py: use dict(x) instead of .copy() on Mapping-typed state
fields; cast where the runtime is a dict but the annotation is
intentionally Mapping (relation databags, remote_grants).
- context.py: raise on missing app_name derivation is deferred (via ''
fallback) so existing tests can assert the downstream error path;
drop dead type: ignore comments now that annotations are correct.
- state.py: widen internal helper params to Mapping to match callers,
split from_context()'s **kwargs TypedDict so it no longer overlaps
with named parameters, annotate default_factory=dict fields with
their element types, replace the bare '' assigned to change_id (a
ChangeID | None) with pebble.ChangeID('').
- _runtime.py: replace deprecated Iterator return type with Generator,
parameterize TemporaryDirectory, normalise Optional unit_id.
- _consistency_checker.py: cast action spec lookups through Any so
pyright can resolve get()/items() types.
- _ops_main_mock.py: initialise loop variable before the try block so
the except branch never sees an unbound name.
Refs canonical#2613
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Follow-up to the TypedDict rename in from_context(): update the nitpick_ignore entry so the docs build stays clean. Refs canonical#2613 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The type annotation was simplified to Sequence[Address], and Sphinx correctly resolves it to ops.testing.Address, so the comment about prefixing is no longer accurate. Refs canonical#2613 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dwilding
left a comment
There was a problem hiding this comment.
I bet finding this was a bit of a shock. Thanks for fixing!
| # If neither ``app_name`` nor a ``name`` in the charm metadata is | ||
| # provided, fall back to an empty string and let the charm code fail | ||
| # later. Raising here would prevent tests from asserting on the | ||
| # downstream failure path (see ``test_init_and_run_with_bad_meta``). |
There was a problem hiding this comment.
By "let the charm code fail", do we mean that the charm code is guaranteed to fail, because of something that Ops tries to do? That's how I interpret the downstream failure path. Just want to check I got the right understanding.
There was a problem hiding this comment.
The comment is badly put. I'll fix.
| # Fields that from_context() forwards through **kwargs — the ones it | ||
| # doesn't already accept explicitly to merge with metadata-derived defaults. |
There was a problem hiding this comment.
The comment wording is a bit confusing. I'm not sure what "to merge with metadata-derived defaults" is meant to apply to. Is that talking about the fields that from_context accepts or the ones that from_context doesn't accept?
There was a problem hiding this comment.
I've reworded, is it clearer now?
| @property | ||
| def _layers(self) -> dict[str, pebble.Layer]: | ||
| return self._container.layers | ||
| def _layers(self) -> dict[str, pebble.Layer]: # pyright: ignore[reportIncompatibleVariableOverride] | ||
| return cast('dict[str, pebble.Layer]', self._container.layers) | ||
|
|
||
| @property | ||
| def _service_status(self) -> dict[str, pebble.ServiceStatus]: | ||
| return self._container.service_statuses | ||
| def _service_status(self) -> dict[str, pebble.ServiceStatus]: # pyright: ignore[reportIncompatibleVariableOverride] | ||
| return cast('dict[str, pebble.ServiceStatus]', self._container.service_statuses) |
There was a problem hiding this comment.
I think I get the point of these. Is this a good-enough workaround for something that really should be changed in the design in the future? If no design change is needed, do you think it's worth adding comments to discourage people from trying to remove the ignores in the future?
There was a problem hiding this comment.
I'll add a comment. I think the best fix will be when we move Harness so it's not in ops by default, and Scenario presumably at that point stops building on top of it.
Merge the two TYPE_CHECKING blocks in _ops_main_mock.py, clarify the app_name fallback comment in context.py, reword the _StateKwargsRest comment in state.py, and add a comment above the pyright ignores on _MockPebbleClient._layers/_service_status explaining the design constraint.
The pyright
includeglobtesting/src/*.pydid not match the subdirectory where the scenario source actually lives (testing/src/scenario/*.py), so that tree was silently unchecked 😞 😢. Add the correct glob and fix the 122 (!) errors that surfaced.Highlights of the fixes:
dict(x)instead of.copy()onMapping-typed state fields;castwhere the runtime is adictbut the annotation is intentionallyMapping(relation databags,remote_grants).type: ignorecomments and default the derivedapp_nameto''so the existing bad-metadata tests still exercise the downstream failure path.Mapping, splitfrom_context()'s**kwargsTypedDict so it no longer overlaps with named parameters, annotatedefault_factory=dictfields with their element types, and replace the bare''assigned toCheckInfo.change_id(ChangeID | None) withpebble.ChangeID('').Iteratorreturn type withGenerator, parameteriseTemporaryDirectory, normalise Optionalunit_id.Anyso pyright can resolveget()/items()types.tryso theexceptbranch never sees an unbound name.Refs #2613