Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
107 changes: 107 additions & 0 deletions .github/skills/generate-test-cases/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
name: generate-test-cases
description: "Use when source code changes under lib/jnpr/junos and the user wants unit tests created or updated for those changes."
allowed-tools:
- read_file
- file_search
- grep_search
- run_in_terminal
- apply_patch
- get_errors
- get_changed_files
context: fork
---

# Generate Unit Tests For PyEZ Source Changes

## Goal
Create or update unit tests for code changes under `lib/jnpr/junos`, matching the existing project style in `tests/unit`.

## Use This Skill When
- The user asks to write unit tests for modified source code.
- The requested scope is changes under `lib/jnpr/junos`.
- The user expects actual test files/edits, not only a coverage review.

## Do Not Use This Skill When
- The task is only test planning or test-case enumeration without code changes.
- The task is functional/integration test authoring under `tests/functional`.

## Required Workflow

### 1) Detect changed source files
Collect changed files and keep only paths under `lib/jnpr/junos`.

Preferred commands:
- `git diff --name-only -- lib/jnpr/junos`
- `git diff --name-only --cached -- lib/jnpr/junos`

If both are empty, ask for the exact files or compare against the upstream default branch:
- `git diff --name-only origin/master...HEAD -- lib/jnpr/junos`

### 2) Map source file to unit test location
Use these conventions first, then adjust to existing repository layout:
- `lib/jnpr/junos/device.py` -> `tests/unit/test_device.py`
- `lib/jnpr/junos/console.py` -> `tests/unit/test_console.py`
- `lib/jnpr/junos/facts/<name>.py` -> `tests/unit/facts/test_<name>.py`
- `lib/jnpr/junos/factory/<name>.py` -> `tests/unit/factory/test_<name>.py`
- `lib/jnpr/junos/utils/<name>.py` -> `tests/unit/utils/test_<name>.py`

If no matching test file exists, create one in the appropriate test subdirectory with naming `test_<module>.py`.

### 3) Learn local testing style before writing
Read nearby tests and follow established conventions:
- `unittest`/`unittest2` style classes and `test_*` methods.
- Heavy use of `unittest.mock` (`patch`, `MagicMock`, `mock_open`).
- Keep fixtures and mocked RPC replies consistent with existing patterns in `tests/unit/**/rpc-reply`.

### 4) Implement tests for behavior, not implementation details
Cover only behavior changed by the source diff.

For each changed behavior, include positive and negative paths when applicable:
- successful path and return value/side effects
- raised exception or error mapping
- edge cases (None, empty input, malformed values)
- compatibility branches if present (for example Python-version or platform branches)

Avoid brittle assertions on internal private state unless that is the public contract used across this repo.

### 5) Keep edits minimal and scoped
- Do not refactor unrelated tests.
- Do not reformat unrelated files.
- Reuse existing helpers and fixtures before adding new ones.

### 6) Validate quickly with targeted test runs
Install required dependencies before executing tests:
- `python3 -m pip install ntc_templates==1.4.1 textfsm==0.4.1 `
- `python3 -m pip install -r requirements.txt`
- `python3 -m pip install nose2 junos-eznc`


Run generated/updated testcase modules first (targeted validation), for example:
- `nose2 -vvv tests.unit.facts.test_<name> --plugin nose2.plugins.junitxml --junit-xml`
- `nose2 -vvv tests.unit.test_<module> --plugin nose2.plugins.junitxml --junit-xml`

Only after targeted tests pass, optionally run the broader unit suite:
- `nose2 -vvv tests.unit --plugin nose2.plugins.junitxml --junit-xml`

### 7) Report results
Summarize:
- changed source files detected
- test files created/updated
- scenarios covered
- test command(s) run and pass/fail outcome
- remaining risks or uncovered branches

Include the generated testcase execution output in verbose format from `nose2 -vvv`.
At minimum, include:
- one line per testcase with status (`... ok` / `... FAIL` / `... ERROR`)
- final summary block (`Ran X tests in Ys` and final status)

## Output Contract
When this skill is invoked, the final response must include:
1. Source-to-test mapping used.
2. Exact test files modified.
3. What behaviors each new/updated test verifies.
4. Validation command summary and outcomes.
5. Verbose generated testcase output (or key excerpt) from `nose2 -vvv`.

56 changes: 56 additions & 0 deletions .github/skills/run-code-quality-tool/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
name: run-code-quality-tool
description: "Use when source code changes under lib/jnpr/junos and the user wants to run static code analysis and code format using pylint and ruff"
allowed-tools:
- run_in_terminal
- read_file
- get_changed_files
- grep_search
context: fork
---

# Run Code Quality Tool

## Goal
Run formatting checks and static analysis for Python changes under `lib/jnpr/junos`.

## Required Workflow

### 1) Install required code-quality tools
Install pylint and ruff before running checks:
- `python3 -m pip install pylint ruff`

### 2) Detect changed Python source files
Collect changed files under `lib/jnpr/junos` and keep only `.py` files.

Preferred commands:
- `git diff --name-only -- lib/jnpr/junos | grep -E '\\.py$'`
- `git diff --name-only --cached -- lib/jnpr/junos | grep -E '\\.py$'`

### 3) Run ruff format check for diff files
Run formatting check for only changed Python files under `lib/jnpr/junos`:
- `CHANGED=$( (git diff --name-only -- lib/jnpr/junos; git diff --name-only --cached -- lib/jnpr/junos) | grep -E '\\.py$' | sort -u )`
- `if [ -n "$CHANGED" ]; then echo "$CHANGED" | xargs -I{} ruff format --check "{}"; else echo "No changed Python files under lib/jnpr/junos"; fi`

### 4) Run pylint for changed files under lib/jnpr/junos
Run pylint for only changed Python files first:
- `CHANGED=$( (git diff --name-only -- lib/jnpr/junos; git diff --name-only --cached -- lib/jnpr/junos) | grep -E '\\.py$' | sort -u )`
- `if [ -n "$CHANGED" ]; then pylint $CHANGED --exit-zero; else echo "No changed Python files under lib/jnpr/junos"; fi`

### 5) Optional full ruff format for all Python files
If requested, run ruff format across all tracked Python files:
- `git ls-files '*.py' | xargs -I{} ruff format "{}"`

### 6) Optional full pylint command
If requested, run the broader pylint command:
- `pylint $(git ls-files '*.py' | grep -vE '^(docs/|build/|tests/|samples/|setup.py|versioneer.py)') --exit-zero`

## Output Contract
When this skill is invoked, the final response must include:
1. Changed Python files detected under `lib/jnpr/junos`.
2. `ruff format --check` result for changed files.
3. `pylint` result for changed files.
4. If optional full ruff format was run, include that result separately.
5. If full pylint was run, include that result separately.


8 changes: 8 additions & 0 deletions lib/jnpr/junos/utils/sw.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,8 @@ def install(
cleanfs_timeout=300,
checksum_timeout=300,
checksum_algorithm="md5",
routing_instance=None,
satellite_name=None,
force_copy=False,
all_re=True,
member_id=None,
Expand Down Expand Up @@ -889,6 +891,12 @@ def _progress(report):

self.log = _progress

if routing_instance is not None and "routing_instance" not in kwargs:
kwargs["routing_instance"] = routing_instance

if satellite_name is not None and "device_list" not in kwargs:
kwargs["device_list"] = satellite_name

# ---------------------------------------------------------------------
# Before doing anything, Do check if any pending install exists.
# ---------------------------------------------------------------------
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/utils/test_sw.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,38 @@ def test_sw_install_kwargs_force_host(self, mock_execute):
etree.tostring(mock_execute.call_args[0][0]).decode("utf-8") in rpc
)

@patch("jnpr.junos.Device.execute")
def test_sw_install_with_routing_instance(self, mock_execute):
self.sw.install("file", no_copy=True, routing_instance="mgmt_junos")
rpc = etree.tostring(mock_execute.call_args[0][0]).decode("utf-8")
self.assertTrue("<routing-instance>mgmt_junos</routing-instance>" in rpc)

@patch("jnpr.junos.Device.execute")
def test_sw_install_with_satellite_name(self, mock_execute):
self.sw.install("file", no_copy=True, satellite_name="sat1")
rpc = etree.tostring(mock_execute.call_args[0][0]).decode("utf-8")
self.assertTrue("<device-list>sat1</device-list>" in rpc)

@patch("jnpr.junos.Device.execute")
def test_sw_install_with_satellite_name_list(self, mock_execute):
self.sw.install("file", no_copy=True, satellite_name=["sat1", "sat2"])
rpc = etree.tostring(mock_execute.call_args[0][0]).decode("utf-8")
self.assertTrue(rpc.count("<device-list>") == 2)
self.assertTrue("<device-list>sat1</device-list>" in rpc)
self.assertTrue("<device-list>sat2</device-list>" in rpc)

@patch("jnpr.junos.Device.execute")
def test_sw_install_issu_with_routing_instance(self, mock_execute):
self.sw.install("file", no_copy=True, issu=True, routing_instance="mgmt_junos")
rpc = etree.tostring(mock_execute.call_args[0][0]).decode("utf-8")
self.assertTrue("<routing-instance>mgmt_junos</routing-instance>" in rpc)

@patch("jnpr.junos.Device.execute")
def test_sw_install_nssu_with_satellite_name(self, mock_execute):
self.sw.install("file", no_copy=True, nssu=True, satellite_name="sat1")
rpc = etree.tostring(mock_execute.call_args[0][0]).decode("utf-8")
self.assertTrue("<device-list>sat1</device-list>" in rpc)

@patch("jnpr.junos.Device.execute")
def test_sw_rollback(self, mock_execute):
rsp = (
Expand Down
Loading