Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/testing/TEST_FORMAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,15 @@ A pytest hook auto-validates during collection:
- Must use assertion helpers, not plain `assert`
- One assertion per test function
- Must use `execute_command()` or helpers from utils

## Remote-Target Tests

Some tests are only meaningful when the client is connecting to a remote server — for example, commands whose behavior differs depending on whether the connection is local. Gate these with the `requires` marker and the `remote_target` capability:

```python
@pytest.mark.requires(remote_target=True)
def test_shutdown_remote_only(collection):
...
```

The harness detects the connection source from the server-side `whatsmyuri` command. If the server reports a localhost address (`localhost`, `127.0.0.1`, `::1`, `0.0.0.0`) or the target cannot be reached, the test is deselected rather than run.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from documentdb_tests.framework.assertions import assertFailure
from documentdb_tests.framework.executor import execute_admin_command

pytestmark = pytest.mark.smoke
pytestmark = [pytest.mark.smoke, pytest.mark.requires(remote_target=True)]


def test_smoke_shutdown(collection):
Expand Down
5 changes: 3 additions & 2 deletions documentdb_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,9 @@ def pytest_collection_modifyitems(session, config, items):
Tests carrying a ``requires`` marker are deselected when their target's
capabilities do not match what the test requires, so they do not run against
a target they do not apply to (rather than appearing as skips). A target's
capabilities are determined by its engine and topology, resolved per target
at runtime (see ``framework.preconditions``).
capabilities are determined by its engine, topology, and connection source,
resolved per target at runtime (see ``framework.preconditions``).

"""
# Deselect a capability-gated test when its target's capabilities do not
# match its requires(...) marker. Each item is parametrized over a target;
Expand Down
20 changes: 19 additions & 1 deletion documentdb_tests/framework/preconditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"local_rename": "renaming into the unreplicated local database is permitted",
"search": "search and vector search surfaces are available",
"replication": "replication commands are available (applyOps, oplog access)",
"remote_target": "the server sees the connection as originating from a non-localhost address",
"validate_repair": (
"validate with repair/fixMultikey is permitted and background validation "
"is rejected (standalone-only behavior)"
Expand All @@ -84,13 +85,15 @@
"search",
"oplog",
"replication",
"remote_target",
}
),
("mongodb", "standalone"): frozenset(
{
"unforced_compact",
"reindex",
"local_rename",
"remote_target",
"validate_repair",
}
),
Expand All @@ -106,6 +109,7 @@
"unforced_compact",
"reindex",
"replication",
"remote_target",
"validate_repair",
}
),
Expand Down Expand Up @@ -162,6 +166,16 @@ def _detect_topology(engine: str, client: MongoClient) -> str:
raise ValueError(f"unknown engine {engine!r}; cannot classify topology")


_LOCALHOST_HOSTS = frozenset({"localhost", "127.0.0.1", "::1", "0.0.0.0"})


def _is_remote_target(client: MongoClient) -> bool:
"""Return whether the server sees this connection as non-localhost."""
result = client.admin.command("whatsmyuri")
server_sees_host = result["you"].rsplit(":", 1)[0]
return server_sees_host not in _LOCALHOST_HOSTS


def marker_spec() -> str:
"""Return the pytest marker definition line for the ``requires`` marker."""
return (
Expand Down Expand Up @@ -189,6 +203,7 @@ def detect_capabilities(engine: str, connection_string: str) -> frozenset[str]:

try:
topology = _detect_topology(engine, client)
is_remote = _is_remote_target(client)
except Exception:
return frozenset()
finally:
Expand All @@ -200,7 +215,10 @@ def detect_capabilities(engine: str, connection_string: str) -> frozenset[str]:
f"no capability mapping for target profile {profile}; "
"add it to _CAPABILITIES_BY_PROFILE"
)
return _CAPABILITIES_BY_PROFILE[profile]
capabilities = set(_CAPABILITIES_BY_PROFILE[profile])
if not is_remote:
capabilities.discard("remote_target")
return frozenset(capabilities)


def unmet_requirements(required: dict[str, bool], capabilities: frozenset[str]) -> dict[str, bool]:
Expand Down
Loading