Skip to content

feat: add --learn mode for expected state capture - #894

Open
ChristopherJHart wants to merge 6 commits into
netascode:mainfrom
ChristopherJHart:feat/learn-mode
Open

ChristopherJHart wants to merge 6 commits into
netascode:mainfrom
ChristopherJHart:feat/learn-mode

Conversation

@ChristopherJHart

Copy link
Copy Markdown
Collaborator

Summary

  • Adds --learn CLI flag that switches test execution from verification to baseline capture mode
  • Implements LearningModeMixin — a contract class that D2D/API tests inherit to define what operational state to capture
  • Captured state is written as YAML to learned_state/ in the working directory, loadable via nac-test -d ./learned_state for verification runs
  • Adds LEARNED_STATE_KEY class variable for consistent namespacing of learned state within the merged data model

Motivation

Operational tests (NRFU) need a two-phase workflow:

  1. Learn: Capture live device state as a known-good baseline (nac-test --learn)
  2. Verify: Compare live state against that baseline (nac-test -d ./learned_state)

This enables "expected state testing" without hardcoding expected values — the network itself defines what's normal.

Changes

File Purpose
cli/main.py --learn flag (+env var NAC_TEST_LEARN)
combined_orchestrator.py Propagate learn flag to PyATS orchestrator
pyats_core/orchestrator.py Pass learn mode to job file generation
pyats_core/common/base_test.py _run_learning_mode() integration in test lifecycle
pyats_core/common/learning_mode_mixin.py LearningModeMixin with capture_learned_state() contract
utils/learned_state.py YAML writer for learned state output
tests/unit/test_learning_mode.py Unit tests (179 lines)

Test plan

  • Unit tests pass (tests/unit/test_learning_mode.py)
  • Tested end-to-end against live NX-OS devices (124+ test scripts, learn + verify)
  • Tested end-to-end against live FTD firewalls (82 test scripts, learn + verify)
  • Verified backward compatibility — existing tests unaffected when --learn is not passed

🤖 Generated with Claude Code

🤖 AI Generation Metadata

  • AI Generated: Yes
  • AI Tool: claude-code
  • AI Model: claude-opus-4-6
  • AI Contribution: ~75%
  • AI Reason: framework implementation + unit tests
  • Human Oversight: Code reviewed and tested by Christopher Hart

ChristopherJHart and others added 5 commits August 15, 2026 19:19
Implements the framework for a two-phase operational test workflow:
1. Learn: `nac-test --learn` captures live state as baseline YAML
2. Verify: `nac-test -d ./learned_state` compares against baseline

New components:
- --learn CLI flag (propagated via NAC_TEST_LEARN env var)
- LearningModeMixin: contract for tests that support learning mode
  (override capture_learned_state() to define what to capture)
- learned_state utilities: save/load YAML files compatible with the
  existing -d merge mechanism (nac_yaml.merge_dict)
- _run_learning_mode() in NACTestBase: branches on env var in
  run_verification_async(), calls capture method, writes output

Tests opt in by inheriting LearningModeMixin and implementing
capture_learned_state(). Tests without it are skipped gracefully
in learn mode. Default (--learn not passed) behavior is unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Meta compat

PyATS's TestableMeta metaclass expects all methods on test classes to have
a .source attribute set during class __init__. Without inheriting from
aetest.Testcase, the mixin's methods lack this attribute, causing
AttributeError at class definition time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of writing `--- {}` (confusing), empty captures now produce a
file with `_learned_state_empty: "reason"`. This distinguishes "learning
ran but found nothing" from "learning was never run for this device."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Learned state files are meant to be version-controlled, not ephemeral.
Changed default output from {output_dir}/learned_state/ to
{cwd}/learned_state/ (repo root). Users pass -d learned_state/ during
verify mode to merge the captured baseline into the data model.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provides a consistent, testable namespace identifier for each learning
test's output within the merged data model. Convention: "{type}.{Class}"
(e.g., "api.VerifyOMPRoutes", "d2d.VerifyBGPLearnedRoutes").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rich/Typer adds ANSI escape codes to --help output on Linux but not
Windows, causing the plain string assertion to fail in CI. Strip
escape codes before checking for --learn flag presence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@oboehmer

Copy link
Copy Markdown
Collaborator

I would like to review the target architecture of this pre-/post comparison with the team. There are existing pre-/post-robot checks for ACI which are triggered by --include tags, so fully opaque to nac-test.
the XR folks also asked about pre-/post, so definitely something to implement, just not sure about this approach

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.

2 participants