Skip to content

Feat/xcp driver#298

Merged
mangelajo merged 4 commits intojumpstarter-dev:mainfrom
vtz:feat/xcp-driver
Mar 19, 2026
Merged

Feat/xcp driver#298
mangelajo merged 4 commits intojumpstarter-dev:mainfrom
vtz:feat/xcp-driver

Conversation

@vtz
Copy link
Contributor

@vtz vtz commented Mar 10, 2026

  • Add jumpstarter-driver-xcp package with full XCP protocol support (measurement, calibration, DAQ, and flash programming) over Ethernet and CAN transports, powered by pyXCP.
  • Add python/examples/xcp-ecu/ with a stateful mock ECU and end-to-end scenario tests, following the same pattern as the automotive UDS example.
  • 54 driver tests + 14 example tests covering happy paths, error handling, config validation, and multi-step stateful workflows through the gRPC boundary.

Summary by CodeRabbit

  • New Features

    • Added full XCP support: driver and client covering sessions, security, memory access, DAQ, and flashing.
  • Examples

    • Added a runnable XCP ECU example with a stateful mock ECU and test fixtures.
  • Documentation

    • Added driver README, API reference entries, and an exporter example config; added example README for the ECU.
  • Tests

    • Added extensive unit and integration tests plus test harnesses covering end-to-end XCP workflows.
  • Chores

    • Packaging/config updates and .gitignore entries for coverage artifacts.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Adds a new jumpstarter-driver-xcp package (driver, client, models), test harnesses (mock server/master, extensive tests), an XCP ECU example with a stateful mock and integration tests, packaging/pyproject files, example configs, and documentation entries. No breaking changes to existing public APIs.

Changes

Cohort / File(s) Summary
Core Driver & Models
python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py, python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/client.py, python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/common.py
New Xcp driver implementation, XcpClient RPC wrapper, and Pydantic models/enums (connection/status/DAQ/program/checksum). Introduces full session/security/memory/DAQ/programming method surface.
Driver Tests & Fixtures
python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/conftest.py, python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver_test.py
Adds MockXcpServer, StatefulXcpMaster, constants/exceptions, and a large test suite (unit + integration) exercising sequencing, state persistence, error paths, and config validation.
XCP ECU Example & Tests
python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/mock_ecu.py, .../conftest.py, .../test_xcp_flow.py, python/examples/xcp-ecu/README.md, python/examples/xcp-ecu/pyproject.toml
Example MockXcpEcu (memory, flash, DAQ, protection), pytest fixtures (mock_ecu, ecu_client), extensive end-to-end example tests, README and example project config for running tests.
Packaging & Workspace
python/packages/jumpstarter-driver-xcp/pyproject.toml, python/packages/jumpstarter-driver-xcp/pyproject.toml (package), python/pyproject.toml, python/packages/jumpstarter-driver-xcp/.gitignore
Adds package pyproject files, declares entry point for driver, workspace member, dependencies (pyxcp, jumpstarter), pytest settings, and .gitignore updates.
Examples & Documentation
python/packages/jumpstarter-driver-xcp/examples/exporter.yaml, python/packages/jumpstarter-driver-xcp/README.md, python/docs/source/reference/package-apis/drivers/xcp.md, python/docs/source/reference/package-apis/drivers/index.md
Adds exporter example YAML, package README documenting usage/config, and docs index/reference entry linking the package README.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant XcpDriver
    participant PyXCPMaster
    participant SlaveECU

    Client->>XcpDriver: connect(mode=0)
    XcpDriver->>PyXCPMaster: master.connect(mode)
    PyXCPMaster->>SlaveECU: XCP CONNECT
    SlaveECU-->>PyXCPMaster: slaveProperties
    PyXCPMaster-->>XcpDriver: connection info
    XcpDriver-->>Client: XcpConnectionInfo
Loading
sequenceDiagram
    participant Client
    participant XcpDriver
    participant PyXCPMaster
    participant SlaveECU

    Client->>XcpDriver: set_mta(address)
    XcpDriver->>PyXCPMaster: master.setMta(address)
    PyXCPMaster->>SlaveECU: XCP SET_MTA
    SlaveECU-->>PyXCPMaster: ack

    Client->>XcpDriver: upload(length,address)
    XcpDriver->>PyXCPMaster: master.shortUpload(length,address)
    PyXCPMaster->>SlaveECU: XCP SHORT_UPLOAD
    SlaveECU-->>PyXCPMaster: data
    PyXCPMaster-->>XcpDriver: bytes
    XcpDriver-->>Client: data
Loading
sequenceDiagram
    participant Client
    participant XcpDriver
    participant PyXCPMaster
    participant SlaveECU

    Client->>XcpDriver: program_start()
    XcpDriver->>PyXCPMaster: master.programStart()
    PyXCPMaster->>SlaveECU: XCP PROGRAM_START
    SlaveECU-->>PyXCPMaster: program info

    Client->>XcpDriver: program_clear(range)
    XcpDriver->>PyXCPMaster: master.programClear(range)
    PyXCPMaster->>SlaveECU: XCP PROGRAM_CLEAR
    SlaveECU-->>PyXCPMaster: ack

    Client->>XcpDriver: program(data)
    loop per block
      XcpDriver->>PyXCPMaster: master.program(block)
      PyXCPMaster->>SlaveECU: XCP PROGRAM (block)
      SlaveECU-->>PyXCPMaster: ack
    end
    Client->>XcpDriver: program_reset()
    XcpDriver->>PyXCPMaster: master.programReset()
    PyXCPMaster->>SlaveECU: XCP PROGRAM_RESET
    SlaveECU-->>PyXCPMaster: ack
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mangelajo
  • NickCao
  • kirkbrauer

Poem

🐰 I hopped through bytes and memory bright,

DAQ and flashing danced in light,
A mock ECU, tests all green,
Drivers wired and docs seen,
The rabbit cheers — XCP takes flight! 🚀

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Feat/xcp driver" is vague and uses generic conventions (Feat/ prefix) but does not clearly convey the specific change; it lacks clarity about what the XCP driver addition entails. Consider using a more descriptive title like "Add XCP driver with Ethernet and CAN support and example ECU" to clearly communicate the main contribution.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver_test.py (1)

411-426: Simplify argument extraction logic.

This complex argument extraction is fragile and hard to maintain. Since you're already checking call_kwargs.kwargs at line 429, the intermediate logic (lines 411-426) appears unused.

♻️ Suggested simplification
     mock_create.assert_called_once()
-    call_kwargs = mock_create.call_args
-    args = call_kwargs.kwargs if call_kwargs.kwargs else {}
-    if not args:
-        args = {
-            "transport": call_kwargs[1].get("transport", call_kwargs[0][0] if call_kwargs[0] else None),
-        }
-        # Extract from positional/keyword args
-        keys = [
-            "transport", "config_file", "host", "port", "protocol",
-            "can_interface", "channel", "bitrate", "can_id_master", "can_id_slave",
-        ]
-        for i, key in enumerate(keys):
-            if i < len(call_kwargs[0]):
-                args[key] = call_kwargs[0][i]
-            elif key in (call_kwargs[1] if len(call_kwargs) > 1 and call_kwargs[1] else {}):
-                args[key] = call_kwargs[1][key]
-
     # The call should use the CAN transport with our specific params
     actual_kwargs = mock_create.call_args.kwargs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver_test.py`
around lines 411 - 426, The current fragile argument-extraction block around
call_kwargs and mock_create (variables call_kwargs, mock_create, args)
reconstructs positional and keyword args manually and is redundant because you
already use call_kwargs.kwargs later; remove the entire manual extraction (the
block that builds args from call_kwargs[0] and call_kwargs[1] and the keys list)
and instead set args directly from call_kwargs.kwargs or call_kwargs.args/kwargs
in a simple, consistent way (e.g., if call_kwargs.kwargs: args =
call_kwargs.kwargs else: map positional call_kwargs.args to expected parameter
names), ensuring subsequent code uses this single args mapping.
python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/conftest.py (1)

417-423: Verify max() argument order for intended behavior.

max(self._daq_lists, 4) returns the larger of the two values. If the intent is "at least 4 DAQ lists available", this is correct. However, if _daq_lists could be 0 (after freeDaq()), this still returns 4, which may not accurately reflect the current allocation state.

💡 Consider clarifying the intent

If the mock should report actual allocation after freeDaq():

-            "processor": {"minDaq": 0, "maxDaq": max(self._daq_lists, 4)},
+            "processor": {"minDaq": 0, "maxDaq": self._daq_lists if self._daq_lists > 0 else 4},

Or add a comment clarifying that 4 is the hardware capability:

+            # maxDaq reflects hardware capability (4) or current allocation
             "processor": {"minDaq": 0, "maxDaq": max(self._daq_lists, 4)},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/conftest.py`
around lines 417 - 423, The getDaqInfo method uses max(self._daq_lists, 4) which
is ambiguous; decide and make intent explicit: if you want the mock to reflect
actual allocation after freeDaq(), return "maxDaq": self._daq_lists (remove the
max), otherwise if 4 represents a hardware minimum keep the max but switch to
max(4, self._daq_lists) and add a clarifying comment; locate getDaqInfo and the
_daq_lists field to implement the chosen change and update the comment
accordingly.
python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py (1)

58-107: Thread-safety limitation: concurrent driver instantiation is unsafe due to pyxcp.cmdline.ArgumentParser mutating process-global state.

The sys.argv save/restore is correctly implemented, but pyxcp.cmdline.ArgumentParser is fundamentally not thread-safe: its constructor modifies sys.argv in-place via parse_and_strip(), and .run() manages a process-global singleton without locking. Concurrent instantiation of multiple Xcp drivers will cause race conditions.

Document this clearly in the docstring and class documentation:

  • Ensure Xcp driver instances are created from a single thread during initialization.
  • If concurrent instantiation is required, serialize driver creation with an explicit lock around the entire _create_xcp_master() function call.

Alternatively, consider adopting pyxcp.config.create_application_from_config() for programmatic configuration to avoid the ArgumentParser CLI entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py`
around lines 58 - 107, The _create_xcp_master function relies on
pyxcp.cmdline.ArgumentParser (XcpArgumentParser) which mutates process-global
state (it calls parse_and_strip on sys.argv and uses a global singleton via
.run()), making concurrent instantiation of drivers unsafe; update the
_create_xcp_master docstring and any class docs for the Xcp driver to explicitly
state that driver instances must be created from a single thread (or that
callers must serialize calls to _create_xcp_master with an external lock), and
add a note suggesting switching to pyxcp.config.create_application_from_config()
for programmatic, thread-safe creation instead of using XcpArgumentParser.
python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/test_xcp_flow.py (1)

93-107: program_clear() isn't being verified through observable behavior.

The flash is already asserted to be erased before program_clear(), so this test mainly proves that _program_cleared flipped. A no-op clear implementation would still pass here. Seed non-erased bytes first, or assert that program_clear() restores an area after it was dirtied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/test_xcp_flow.py` around
lines 93 - 107, The test currently only checks that mock_ecu._program_cleared
flipped; instead seed the flash with non-erased data at FLASH_BASE (e.g., write
a non-zero pattern using the ECU client's write/program method or directly
manipulate mock_ecu storage), verify upload(4, FLASH_BASE, 0) returns that
non-zero data, then call ecu_client.program_clear(FLASH_SIZE) and assert that
subsequent _to_bytes(ecu_client.upload(4, FLASH_BASE, 0)) returns all zeros and
that mock_ecu._program_cleared is True; reference ecu_client.upload,
ecu_client.program_clear, FLASH_BASE and FLASH_SIZE to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/mock_ecu.py`:
- Around line 298-303: The flash erased state is inconsistent: flash is
initialized elsewhere using b"\x00" but program() falls back to b"\xFF"; update
the fallback in program() to use the same erased byte as initialization (replace
b"\xFF" with b"\x00") or, better, reference a single constant (e.g.,
FLASH_ERASED_BYTE) so FLASH_BASE, FLASH_SIZE and self._memory use the same
erased-state value across initialization and the program() write path.

In `@python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/test_xcp_flow.py`:
- Around line 223-233: The test test_calibration_write_without_unlock_succeeds
currently asserts that a calibration write succeeds without unlocking which
contradicts the README and weakens the protection flow; update the test so it
requires calling ecu_client.unlock() (or otherwise set
mock_ecu._protection["calpag"] = False via the existing unlock flow) before
performing ecu_client.download and ecu_client.upload, and then assert the write
persists, and adjust any checks that inspect mock_ecu._protection to reflect
that unlock() must be called for calibration writes to succeed.

---

Nitpick comments:
In `@python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/test_xcp_flow.py`:
- Around line 93-107: The test currently only checks that
mock_ecu._program_cleared flipped; instead seed the flash with non-erased data
at FLASH_BASE (e.g., write a non-zero pattern using the ECU client's
write/program method or directly manipulate mock_ecu storage), verify upload(4,
FLASH_BASE, 0) returns that non-zero data, then call
ecu_client.program_clear(FLASH_SIZE) and assert that subsequent
_to_bytes(ecu_client.upload(4, FLASH_BASE, 0)) returns all zeros and that
mock_ecu._program_cleared is True; reference ecu_client.upload,
ecu_client.program_clear, FLASH_BASE and FLASH_SIZE to locate the change.

In `@python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/conftest.py`:
- Around line 417-423: The getDaqInfo method uses max(self._daq_lists, 4) which
is ambiguous; decide and make intent explicit: if you want the mock to reflect
actual allocation after freeDaq(), return "maxDaq": self._daq_lists (remove the
max), otherwise if 4 represents a hardware minimum keep the max but switch to
max(4, self._daq_lists) and add a clarifying comment; locate getDaqInfo and the
_daq_lists field to implement the chosen change and update the comment
accordingly.

In
`@python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver_test.py`:
- Around line 411-426: The current fragile argument-extraction block around
call_kwargs and mock_create (variables call_kwargs, mock_create, args)
reconstructs positional and keyword args manually and is redundant because you
already use call_kwargs.kwargs later; remove the entire manual extraction (the
block that builds args from call_kwargs[0] and call_kwargs[1] and the keys list)
and instead set args directly from call_kwargs.kwargs or call_kwargs.args/kwargs
in a simple, consistent way (e.g., if call_kwargs.kwargs: args =
call_kwargs.kwargs else: map positional call_kwargs.args to expected parameter
names), ensuring subsequent code uses this single args mapping.

In `@python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py`:
- Around line 58-107: The _create_xcp_master function relies on
pyxcp.cmdline.ArgumentParser (XcpArgumentParser) which mutates process-global
state (it calls parse_and_strip on sys.argv and uses a global singleton via
.run()), making concurrent instantiation of drivers unsafe; update the
_create_xcp_master docstring and any class docs for the Xcp driver to explicitly
state that driver instances must be created from a single thread (or that
callers must serialize calls to _create_xcp_master with an external lock), and
add a note suggesting switching to pyxcp.config.create_application_from_config()
for programmatic, thread-safe creation instead of using XcpArgumentParser.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4383504c-7554-4c96-aade-148b97b44613

📥 Commits

Reviewing files that changed from the base of the PR and between 5bfa067 and 1639ef8.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • python/docs/source/reference/package-apis/drivers/xcp.md
  • python/examples/xcp-ecu/README.md
  • python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/__init__.py
  • python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/conftest.py
  • python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/mock_ecu.py
  • python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/test_xcp_flow.py
  • python/examples/xcp-ecu/pyproject.toml
  • python/packages/jumpstarter-driver-xcp/.gitignore
  • python/packages/jumpstarter-driver-xcp/README.md
  • python/packages/jumpstarter-driver-xcp/examples/exporter.yaml
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/__init__.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/client.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/common.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/conftest.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver_test.py
  • python/packages/jumpstarter-driver-xcp/pyproject.toml
  • python/pyproject.toml

@netlify
Copy link

netlify bot commented Mar 15, 2026

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit f6ed248
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/69b82ca6831bb80008878198
😎 Deploy Preview https://deploy-preview-298--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vtz
Copy link
Contributor Author

vtz commented Mar 15, 2026

Addressed all coderabbit comments.

Copy link
Member

@mangelajo mangelajo left a comment

Choose a reason for hiding this comment

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

document in index

vtz added 4 commits March 16, 2026 12:11
Wraps the pyXCP library to provide remote measurement, calibration,
DAQ, and programming of XCP-enabled ECUs over Ethernet, CAN, USB,
and Serial transports.
Add StatefulXcpMaster to driver tests (16 new tests) verifying
connection state, memory round-trips, DAQ allocation, programming
sequence enforcement, and reconnect behavior through the gRPC boundary.

Add python/examples/xcp-ecu/ example package (14 scenario tests)
modeled after the automotive UDS example, with a mock ECU that
simulates calibration parameters, measurement signals, flash
programming, and resource protection.
- Fix flash erased state inconsistency in mock_ecu: introduce
  FLASH_ERASED_BYTE constant and use it consistently across init,
  programClear, and program (was using b"\xFF" fallback vs b"\x00")
- Enforce CAL/PAG protection on mock ECU downloads and update
  test_calibration_write to require unlock before writing
- Improve test_full_flash_programming_workflow to seed non-erased data
  before program_clear, verifying erasure actually works
- Clarify getDaqInfo max() intent with reordered args and comment
- Remove dead argument-extraction code in test_custom_config_forwarded
- Add thread-safety warning to _create_xcp_master docstring
@vtz vtz force-pushed the feat/xcp-driver branch from 67f9a00 to f6ed248 Compare March 16, 2026 16:15
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py (1)

58-112: Thread-safety caveat is well-documented, but consider adding a lock.

The warning about sys.argv manipulation and pyxcp's ArgumentParser not being thread-safe is clear (lines 75-80). However, if multiple Xcp driver instances are created concurrently (e.g., in a multi-threaded test runner or when multiple drivers are served simultaneously), the sys.argv manipulation could cause race conditions.

Consider adding a module-level lock to serialize master creation:

♻️ Optional: Add lock for thread safety
+import threading
+
+_create_master_lock = threading.Lock()
+
 def _create_xcp_master(
     transport: XcpTransport,
     ...
 ):
     ...
+    with _create_master_lock:
         saved_argv = sys.argv
         sys.argv = [
             "jumpstarter-xcp",
             "-t", transport.value.lower(),
             "--config", cfg_path,
         ]
         try:
             ap = XcpArgumentParser(description="Jumpstarter XCP Driver")
             master = ap.run()
         finally:
             sys.argv = saved_argv
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py`
around lines 58 - 112, Add a module-level threading.Lock (e.g.,
_xcp_master_lock) and acquire it around the critical section in
_create_xcp_master that mutates sys.argv and invokes XcpArgumentParser.run(),
releasing it in the outer finally to ensure serialization; keep tmp_path
creation and cleanup as-is and ensure saved_argv restoration and master creation
remain inside the locked region so sys.argv is never concurrently modified while
XcpArgumentParser is parsing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/packages/jumpstarter-driver-xcp/pyproject.toml`:
- Around line 27-45: The pytest config sets asyncio_mode = "auto" but
pytest-asyncio is not declared in the dev dependency group; add
"pytest-asyncio>=0.20.0" (or compatible minimum) to the dependency-groups dev
list so pytest can run async tests, updating the dev group in pyproject.toml
where dependency-groups and asyncio_mode are defined and ensuring the package is
installed in CI/dev environments.

---

Nitpick comments:
In `@python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py`:
- Around line 58-112: Add a module-level threading.Lock (e.g., _xcp_master_lock)
and acquire it around the critical section in _create_xcp_master that mutates
sys.argv and invokes XcpArgumentParser.run(), releasing it in the outer finally
to ensure serialization; keep tmp_path creation and cleanup as-is and ensure
saved_argv restoration and master creation remain inside the locked region so
sys.argv is never concurrently modified while XcpArgumentParser is parsing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99dcc71d-36ef-4d2e-89e5-efa0af571549

📥 Commits

Reviewing files that changed from the base of the PR and between 67f9a00 and f6ed248.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • python/docs/source/reference/package-apis/drivers/index.md
  • python/docs/source/reference/package-apis/drivers/xcp.md
  • python/examples/xcp-ecu/README.md
  • python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/__init__.py
  • python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/conftest.py
  • python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/mock_ecu.py
  • python/examples/xcp-ecu/jumpstarter_example_xcp_ecu/test_xcp_flow.py
  • python/examples/xcp-ecu/pyproject.toml
  • python/packages/jumpstarter-driver-xcp/.gitignore
  • python/packages/jumpstarter-driver-xcp/README.md
  • python/packages/jumpstarter-driver-xcp/examples/exporter.yaml
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/__init__.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/client.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/common.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/conftest.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver.py
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/driver_test.py
  • python/packages/jumpstarter-driver-xcp/pyproject.toml
  • python/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (8)
  • python/packages/jumpstarter-driver-xcp/README.md
  • python/packages/jumpstarter-driver-xcp/.gitignore
  • python/packages/jumpstarter-driver-xcp/examples/exporter.yaml
  • python/docs/source/reference/package-apis/drivers/xcp.md
  • python/docs/source/reference/package-apis/drivers/index.md
  • python/pyproject.toml
  • python/packages/jumpstarter-driver-xcp/jumpstarter_driver_xcp/common.py
  • python/examples/xcp-ecu/README.md

Comment on lines +27 to +45
[tool.pytest.ini_options]
addopts = "--cov --cov-report=html --cov-report=xml"
log_cli = true
log_cli_level = "INFO"
testpaths = ["jumpstarter_driver_xcp"]
asyncio_mode = "auto"

[build-system]
requires = ["hatchling", "hatch-vcs", "hatch-pin-jumpstarter"]
build-backend = "hatchling.build"

[tool.hatch.build.hooks.pin_jumpstarter]
name = "pin_jumpstarter"

[dependency-groups]
dev = [
"pytest-cov>=6.0.0",
"pytest>=8.3.3",
]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing pytest-asyncio dependency for asyncio_mode = "auto".

The pytest configuration specifies asyncio_mode = "auto" (line 32), which requires pytest-asyncio to be installed. However, it's not listed in the dev dependencies.

🛠️ Proposed fix
 [dependency-groups]
 dev = [
     "pytest-cov>=6.0.0",
     "pytest>=8.3.3",
+    "pytest-asyncio>=0.23.0",
 ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[tool.pytest.ini_options]
addopts = "--cov --cov-report=html --cov-report=xml"
log_cli = true
log_cli_level = "INFO"
testpaths = ["jumpstarter_driver_xcp"]
asyncio_mode = "auto"
[build-system]
requires = ["hatchling", "hatch-vcs", "hatch-pin-jumpstarter"]
build-backend = "hatchling.build"
[tool.hatch.build.hooks.pin_jumpstarter]
name = "pin_jumpstarter"
[dependency-groups]
dev = [
"pytest-cov>=6.0.0",
"pytest>=8.3.3",
]
[tool.pytest.ini_options]
addopts = "--cov --cov-report=html --cov-report=xml"
log_cli = true
log_cli_level = "INFO"
testpaths = ["jumpstarter_driver_xcp"]
asyncio_mode = "auto"
[build-system]
requires = ["hatchling", "hatch-vcs", "hatch-pin-jumpstarter"]
build-backend = "hatchling.build"
[tool.hatch.build.hooks.pin_jumpstarter]
name = "pin_jumpstarter"
[dependency-groups]
dev = [
"pytest-cov>=6.0.0",
"pytest>=8.3.3",
"pytest-asyncio>=0.23.0",
]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter-driver-xcp/pyproject.toml` around lines 27 - 45,
The pytest config sets asyncio_mode = "auto" but pytest-asyncio is not declared
in the dev dependency group; add "pytest-asyncio>=0.20.0" (or compatible
minimum) to the dependency-groups dev list so pytest can run async tests,
updating the dev group in pyproject.toml where dependency-groups and
asyncio_mode are defined and ensuring the package is installed in CI/dev
environments.

@mangelajo mangelajo enabled auto-merge (squash) March 19, 2026 11:21
@mangelajo mangelajo merged commit 8c77f8f into jumpstarter-dev:main Mar 19, 2026
31 checks passed
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