Skip to content

LLT-7043: pytest fixtures for helpers#1714

Open
gytsto wants to merge 23 commits intomainfrom
LLT-7043_pytest_fixtures_for_helpers
Open

LLT-7043: pytest fixtures for helpers#1714
gytsto wants to merge 23 commits intomainfrom
LLT-7043_pytest_fixtures_for_helpers

Conversation

@gytsto
Copy link
Copy Markdown
Contributor

@gytsto gytsto commented Mar 18, 2026

Problem

Nat-lab test files use a factory-pattern for environment setup where each test manually calls setup_mesh_nodes() or setup_environment() at runtime, managing AsyncExitStack lifecycle inline. This leads to:

  • Environment setup and teardown time is included in the test's reported runtime, inflating test durations and making it difficult to distinguish actual test logic performance from infrastructure overhead
  • Duplicated setup/teardown boilerplate across every test function
  • Inconsistent error handling during teardown (e.g. iptables cleanup failures crashing teardown)
  • Proliferation of specialized fixture variants (env_vpn_conntracked, env_pq_nlx_vpn, env_pq_nlx_vpn_rekey, etc.) that differ only in minor parameter mutations
  • Tight coupling between test logic and infrastructure orchestration, making tests harder to read and maintain

Solution

Introduce a helpers_fixtures.py module with two generic setup-phase fixtures — env_mesh and env — that handle all environment lifecycle in pytest's fixture phase. By moving setup into fixtures, environment provisioning and teardown time is no longer counted as part of the test's execution duration, giving more accurate test timing metrics. Key design decisions:

  • Dynamic node resolution: _resolve_setup_params() uses request.getfixturevalue() to auto-detect which alpha_setup_params, beta_setup_params, gamma_setup_params are parametrized on the test, eliminating the need for separate 1-node/2-node/3-node fixture variants
  • Compositional vpn_tags fixture: A default fixture returning [] that tests override at class/module level to inject VPN server connections, replacing dedicated VPN-specific fixtures
  • Autouse mutation fixtures: Test classes define small autouse fixtures (e.g. _mutate_pq_version, _mutate_conntracker) to apply parameter mutations, replacing 5 specialized environment fixtures with the generic env/env + composition
  • Robust teardown: linux_router.py wraps all iptables teardown commands in try/except ProcessExecError to prevent cascading failures

This reduces the fixture count from 22 specialized fixtures to 2 generic ones (plus 1 kept specialized: env_mesh_3node_ring_fw which requires pre-setup API manipulation). Each of the 19 converted test files gets its own commit for reviewability.

☑️ Definition of Done checklist

  • Commit history is clean (requirements)
  • README.md is updated
  • Functionality is covered by unit or integration tests

@gytsto gytsto requested a review from a team as a code owner March 18, 2026 09:08
@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from 091ce6f to ca25ce6 Compare March 18, 2026 09:43
@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from ca25ce6 to 6cc08c4 Compare March 18, 2026 11:01
@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from 6cc08c4 to 1beffaa Compare March 18, 2026 11:29
@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from 4bcfab8 to e05187e Compare March 19, 2026 07:53
@gytsto gytsto marked this pull request as draft March 19, 2026 08:23
@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from 26a88cd to 7c346ca Compare March 27, 2026 07:20
@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from 7c346ca to 2c3f2ac Compare March 27, 2026 08:31
@gytsto gytsto marked this pull request as ready for review March 27, 2026 10:07
Comment thread nat-lab/tests/test_pq.py Outdated
assert (
preshared_after != preshared_before
), "Preshared key not changed on the nlx server"
preshared_after = inspect_preshared_key(nlx_conn)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be await'ed ..? Or no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like it should, but even before my changes it was not awaited, so idk how it worked 😄 will fix it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fixed now

@gytsto gytsto changed the title Llt 7043 pytest fixtures for helpers LLT-7043: pytest fixtures for helpers Apr 14, 2026
Comment on lines +403 to +405
except ProcessExecError:
log.warning(
"disable_path cleanup: INPUT rule for %s already removed",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check for the ProcessExecError error message like we do in DockerProcess, in case it failed with for some other reason? Otherwise log or re-raise the Exception

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

inspect = await self._execute.inspect()
await asyncio.sleep(0.01)
if inspect["ExitCode"] is None:
proc = subprocess.run(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is subrocess.run() a blocking call?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was, fixed

"[%s] Cleanup failed: %s",
while inspect["Pid"] == 0 and inspect["ExitCode"] is None:
inspect = await self._execute.inspect()
await asyncio.sleep(0.01)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can use await asyncio.wait_for() somehow to avoid the spin lock with the sleep()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

Comment thread nat-lab/tests/helpers_fixtures.py Outdated
def _resolve_setup_params(request: pytest.FixtureRequest) -> List[SetupParameters]:
"""Dynamically resolve available *_setup_params fixtures.

Tries alpha, beta, gamma in order. Stops at the first missing fixture.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a case where we can expect only "alpha" and "gamma" to be defined?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there usually is not, but you pin pointed an issue, fixed

@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from 6bb1383 to 0e4a66e Compare April 16, 2026 09:57
@matislovas
Copy link
Copy Markdown
Collaborator

+1

@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from 0e4a66e to e8e2694 Compare April 27, 2026 06:42
gytsto added 23 commits April 27, 2026 17:42
Add helpers_fixtures.py with generic env/env_mesh fixtures that use
dynamic setup parameter resolution via _resolve_setup_params().
Includes vpn_tags compositional fixture pattern and supporting
utility changes to docker_process.py and linux_router.py.
Convert test_vpn.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_mesh_plus_vpn.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_pq.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_adapter.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
…es.py

Convert test_connection_states.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_direct_feature.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
…t.py

Convert test_dns_through_exit.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
…_event.py

Convert test_fire_connecting_event.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
…rt.py

Convert test_flame_graph_chart.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_mesh_firewall.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
…e.py

Convert test_mesh_remove_node.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_meshnet_id.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_neptun.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_network_monitor.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
…kering.py

Convert test_node_state_flickering.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_pinging.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_proxy.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_reconnections.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
Convert test_telio_tasks.py to use setup-phase env/env_mesh fixtures with
compositional vpn_tags and autouse mutation fixtures instead of
factory-pattern runtime setup.
inspect_preshared_key is an async function, but was called without
await. This caused the calls to return coroutine objects instead of
executing the function body, silently skipping the internal preshared
key assertions and making the rekey comparison always pass vacuously.
- helpers_fixtures.py: Use continue instead of break in _resolve_setup_params()
  to allow non-consecutive node configurations (e.g., alpha+gamma without beta)

- linux_router.py: Check e.stderr content in ProcessExecError handlers
  before swallowing iptables cleanup errors, re-raise unexpected errors
  to avoid silently hiding real failures

- docker_process.py: Replace blocking subprocess.run() with async
  asyncio.create_subprocess_exec() to avoid blocking the event loop
  during exec cleanup

- docker_process.py: Replace spin lock with asyncio.wait_for() using
  a 10-second timeout to prevent infinite spinning, and extract
  _wait_for_process_start() and _kill_exec_if_running() helper methods
  to consolidate duplicated cleanup logic
@gytsto gytsto force-pushed the LLT-7043_pytest_fixtures_for_helpers branch from e8e2694 to 98d4a91 Compare April 27, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants