Skip to content

fix(e2e): resolve marker MRO mismatch and add missing model marker - #569

Merged
slin1237 merged 9 commits into
mainfrom
keyang/fix-oss-test
Mar 2, 2026
Merged

slin1237 merged 9 commits into
mainfrom
keyang/fix-oss-test

Conversation

@key4ng

@key4ng key4ng commented Feb 28, 2026 •

Copy link
Copy Markdown
Member

Description

Problem

Two bugs in the E2E test infrastructure cause tests to silently run against wrong models:

  1. Marker resolution mismatch: get_marker_value() and get_marker_kwargs() use get_closest_marker() which doesn't respect class MRO for inherited markers. The hooks layer walks MRO correctly during collection, but the fixture layer doesn't — so collection-time scanning sees the child's model while runtime fixtures resolve to the parent's model. This affects TestChatCompletionGptOss (16 tests running on Llama-8B instead of gpt-oss-20b).

  2. Missing model marker: TestBuiltinToolRoutingGrpc lacks @pytest.mark.model(...), so its model isn't pre-scanned during collection. It only works by accident when another test class launches the same model.

Solution

Add MRO-aware marker resolution to the fixture layer (matching what hooks already did), extract the shared logic into a reusable helper, and add the missing marker.

Changes

  • e2e_test/fixtures/markers.py: Add resolve_class_marker() helper that walks cls.__mro__ child-first before falling back to get_closest_marker(). Update get_marker_value() and get_marker_kwargs() to use it instead of get_closest_marker() directly.
  • e2e_test/fixtures/hooks.py: Replace 15-line inline MRO walk in pytest_collection_modifyitems with a 2-line call to resolve_class_marker(), eliminating code duplication.
  • e2e_test/responses/test_builtin_tools.py: Add @pytest.mark.model("openai/gpt-oss-20b") to TestBuiltinToolRoutingGrpc.

Test Plan

  • ruff check, ruff format --check, and mypy all pass on the changed files.
  • Verified no other inherited test classes are affected (_TestToolChoiceBase children are fine since the base has no markers).

Summary by CodeRabbit

  • Tests

    • Improved marker resolution to be MRO-aware for class-based tests, making test configuration more reliable.
    • Added model-specific markers to certain test classes.
    • Increased token limits for stop-sequence tests and added several skipped Harmony-related test stubs for future validation.
  • Chores

    • Consolidated and simplified test marker resolution logic for clearer, more maintainable test behavior.

@coderabbitai

coderabbitai Bot commented Feb 28, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an MRO-aware pytest marker resolver and updates fixtures to use it; adjusts marker-related helper signatures; annotates two test classes with a model marker; increases token limits and adds skipped Harmony-related test stubs in chat completion tests.

Changes

Cohort / File(s) Summary
Marker resolution core
e2e_test/fixtures/markers.py
Added resolve_class_marker(item, marker_name) (MRO-aware class marker lookup). Updated get_marker_value() signature to accept arg_index and default, and get_marker_kwargs() to accept defaults; both now use resolve_class_marker() for class-based tests.
Hook usage
e2e_test/fixtures/hooks.py
Replaced manual MRO traversal / get_closest_marker logic with resolve_class_marker(); simplified model_id extraction from marker args and removed explicit per-class loop.
Test annotations
e2e_test/responses/test_builtin_tools.py
Added @pytest.mark.model("openai/gpt-oss-20b") decorators to TestBuiltinToolRoutingGrpc and TestWebSearchStreamingEventsGrpc classes.
Chat completion tests
e2e_test/chat_completions/test_openai_server.py
Increased stop-sequence token limits (non-stream 50→200, stream 50→1024). Added several skipped Harmony-related test stubs in TestChatCompletion and TestChatCompletionGptOss (stop-sequences and streaming token-count tests).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through classes, sniffed each mark,
Found the child-first path out of the dark,
I nudged fixtures tidy, left tests with a wink,
Tokens grew taller, and stubs took a blink,
A rabbit's small patch—clean, clever, and smart. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: fixing a marker MRO resolution issue in fixtures and adding a missing model marker to a test class.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch keyang/fix-oss-test

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

@github-actions github-actions Bot added the tests Test changes label Feb 28, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors pytest marker resolution logic to correctly handle class inheritance, ensuring that markers defined on child classes take precedence over those on parent classes. This enhancement centralizes marker resolution into a new utility function, resolve_class_marker, which is then integrated into existing marker retrieval functions and test setup hooks. The change improves the robustness and predictability of test configurations, particularly for tests utilizing class hierarchies.

Highlights

  • MRO-aware Marker Resolution: Introduced a new resolve_class_marker function to correctly handle pytest marker resolution in class inheritance hierarchies, ensuring child class markers take precedence over parent class markers.
  • Marker Utility Updates: Updated existing utility functions get_marker_value and get_marker_kwargs to leverage the new MRO-aware resolve_class_marker for consistent behavior.
  • Simplified Marker Extraction: Refactored the model marker extraction logic within calculate_test_gpus in e2e_test/fixtures/hooks.py by replacing manual MRO traversal with a call to the new resolve_class_marker.
  • Integration Test Marker: Added a pytest.mark.model marker to the TestBuiltinToolRoutingGrpc class to specify the model for integration testing.
Changelog
  • e2e_test/fixtures/hooks.py
    • Replaced manual MRO traversal for model marker extraction with a call to the new resolve_class_marker function.
    • Imported resolve_class_marker from .markers.
  • e2e_test/fixtures/markers.py
    • Added a new function resolve_class_marker to handle MRO-aware pytest marker resolution for pytest.Item nodes.
    • Modified get_marker_value to use resolve_class_marker instead of request.node.get_closest_marker.
    • Modified get_marker_kwargs to use resolve_class_marker instead of request.node.get_closest_marker.
    • Updated docstrings for get_marker_value and get_marker_kwargs to reflect MRO-aware resolution.
  • e2e_test/responses/test_builtin_tools.py
    • Applied the pytest.mark.model("openai/gpt-oss-20b") marker to the TestBuiltinToolRoutingGrpc class.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a helpful refactoring to centralize MRO-aware marker resolution for pytest, improving code clarity and maintainability. The new resolve_class_marker function correctly handles marker precedence in class inheritance. My review includes a fix for a potential bug in this new function that could affect keyword-argument-only markers, and a minor performance improvement suggestion regarding an import statement placement.

Comment thread e2e_test/fixtures/markers.py Outdated
Comment thread e2e_test/fixtures/hooks.py Outdated
@key4ng key4ng changed the title refactor(tests): enhance marker resolution for class inheritance in p… fix(e2e): resolve marker MRO mismatch and add missing model marker Feb 28, 2026
@key4ng key4ng changed the title fix(e2e): resolve marker MRO mismatch and add missing model marker fix(e2e): resolve marker MRO mismatch and add missing model marke Feb 28, 2026
@key4ng

key4ng commented Feb 28, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Feb 28, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@key4ng key4ng changed the title fix(e2e): resolve marker MRO mismatch and add missing model marke fix(e2e): resolve marker MRO mismatch and add missing model marker Feb 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e_test/fixtures/markers.py`:
- Around line 25-32: The condition that checks "and marker.args" inside the loop
that inspects class-level pytest marks prevents detecting kwargs-only markers;
update the logic in the function that iterates item.cls.__mro__ (the block that
builds "markers" from cls.pytestmark and checks "if marker.name == marker_name
and marker.args") to remove the "and marker.args" clause so markers with only
kwargs (e.g., `@pytest.mark.workers`(count=1)) are returned; keep the rest of the
behavior the same (get_marker_value still guards on marker.args elsewhere).

In `@e2e_test/responses/test_builtin_tools.py`:
- Around line 624-626: Add the same pytest model marker to the
TestWebSearchStreamingEventsGrpc class: above the class definition for
TestWebSearchStreamingEventsGrpc, add `@pytest.mark.model`("openai/gpt-oss-20b")
(matching the marker used on TestBuiltinToolRoutingGrpc) so that tests using the
gateway_with_mcp_config_grpc fixture are pre-scanned with the correct model;
ensure the decorator is applied directly above the class definition.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e19218 and 040392c.

📒 Files selected for processing (3)
  • e2e_test/fixtures/hooks.py
  • e2e_test/fixtures/markers.py
  • e2e_test/responses/test_builtin_tools.py

Comment thread e2e_test/responses/test_builtin_tools.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
e2e_test/responses/test_builtin_tools.py (1)

695-696: ⚠️ Potential issue | 🟠 Major

TestWebSearchStreamingEventsGrpc also needs the model marker.

This class uses the same gateway_with_mcp_config_grpc fixture (which requests "openai/gpt-oss-20b" from the model pool) but lacks the @pytest.mark.model marker. Without it, the model won't be pre-scanned during collection, causing the same issue this PR aims to fix.

🔧 Proposed fix
 `@pytest.mark.e2e`
+@pytest.mark.model("openai/gpt-oss-20b")
 class TestWebSearchStreamingEventsGrpc:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e_test/responses/test_builtin_tools.py` around lines 695 - 696, Add the
pytest model marker to the TestWebSearchStreamingEventsGrpc test class so the
model is pre-scanned during collection; specifically, annotate the class with
the same pytest.mark.model marker used elsewhere (the model name requested by
the gateway_with_mcp_config_grpc fixture, e.g., "openai/gpt-oss-20b") so tests
using the gateway_with_mcp_config_grpc fixture get the model pre-scanned.
e2e_test/fixtures/markers.py (1)

25-32: ⚠️ Potential issue | 🟠 Major

The and marker.args condition breaks kwargs-only marker resolution.

The condition on line 30 filters out markers that have no positional arguments, which breaks get_marker_kwargs() for kwargs-only markers like @pytest.mark.workers(count=1) and @pytest.mark.gateway(policy="round_robin"). Since get_marker_value() already guards on marker.args at line 58, this condition is redundant for that caller and harmful for get_marker_kwargs().

🔧 Proposed fix
     if hasattr(item, "cls") and item.cls is not None:
         for cls in item.cls.__mro__:
             if hasattr(cls, "pytestmark"):
                 markers = cls.pytestmark if isinstance(cls.pytestmark, list) else [cls.pytestmark]
                 for marker in markers:
-                    if marker.name == marker_name and marker.args:
+                    if marker.name == marker_name:
                         return marker
     return item.get_closest_marker(marker_name)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e_test/fixtures/markers.py` around lines 25 - 32, The check "and
marker.args" in the class-mark scanning block inside markers.py incorrectly
filters out kwargs-only pytest markers; remove that condition so the loop
returns markers regardless of positional args (i.e., change the conditional that
currently reads marker.name == marker_name and marker.args to only check
marker.name == marker_name). This preserves behavior for get_marker_kwargs()
while get_marker_value() can still guard on marker.args where needed; update the
logic around the class-level pytestmark handling in the same function so markers
with only keyword args (e.g., `@pytest.mark.workers`(count=1)) are returned.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e_test/fixtures/hooks.py`:
- Around line 135-139: The import of resolve_class_marker is inside the loop;
move "from .markers import resolve_class_marker" out of the loop (either to
module top or just before iterating over items_to_scan) and remove the in-loop
import, then keep using resolve_class_marker(item, PARAM_MODEL) and
model_marker.args access as-is so the behavior with PARAM_MODEL and model_marker
remains unchanged.

---

Duplicate comments:
In `@e2e_test/fixtures/markers.py`:
- Around line 25-32: The check "and marker.args" in the class-mark scanning
block inside markers.py incorrectly filters out kwargs-only pytest markers;
remove that condition so the loop returns markers regardless of positional args
(i.e., change the conditional that currently reads marker.name == marker_name
and marker.args to only check marker.name == marker_name). This preserves
behavior for get_marker_kwargs() while get_marker_value() can still guard on
marker.args where needed; update the logic around the class-level pytestmark
handling in the same function so markers with only keyword args (e.g.,
`@pytest.mark.workers`(count=1)) are returned.

In `@e2e_test/responses/test_builtin_tools.py`:
- Around line 695-696: Add the pytest model marker to the
TestWebSearchStreamingEventsGrpc test class so the model is pre-scanned during
collection; specifically, annotate the class with the same pytest.mark.model
marker used elsewhere (the model name requested by the
gateway_with_mcp_config_grpc fixture, e.g., "openai/gpt-oss-20b") so tests using
the gateway_with_mcp_config_grpc fixture get the model pre-scanned.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e19218 and 040392c.

📒 Files selected for processing (3)
  • e2e_test/fixtures/hooks.py
  • e2e_test/fixtures/markers.py
  • e2e_test/responses/test_builtin_tools.py

Comment thread e2e_test/fixtures/hooks.py
@github-actions github-actions Bot added grpc gRPC client and router changes model-gateway Model gateway crate changes labels Feb 28, 2026
@key4ng
key4ng force-pushed the keyang/fix-oss-test branch from 653a386 to 4fd6780 Compare February 28, 2026 22:00
@github-actions github-actions Bot removed grpc gRPC client and router changes model-gateway Model gateway crate changes labels Feb 28, 2026
@key4ng
key4ng marked this pull request as ready for review March 2, 2026 04:22
key4ng added 9 commits March 1, 2026 20:22
…ytest

- Introduced `resolve_class_marker` function to improve MRO-aware marker resolution, ensuring child class markers take precedence over parent class markers.
- Updated `get_marker_value` and `get_marker_kwargs` to utilize the new resolution method.
- Simplified marker extraction logic in `pytest_collection_modifyitems` to enhance readability and maintainability.
- Added model marker to `TestBuiltinToolRoutingGrpc` for integration testing.

Signed-off-by: key4ng <rukeyang@gmail.com>
…yParserAdapter

- Added `extract_stop_string` method to retrieve stop strings from matched_stop JSON values.
- Introduced `trim_stop_sequence` method to remove trailing stop sequences from text outputs.
- Integrated stop string trimming in both `parse_complete` and `parse_incomplete` methods to ensure clean output.

Signed-off-by: [Your Name] [Your Email]
Signed-off-by: key4ng <rukeyang@gmail.com>
…tions

- Implemented tests for handling stop sequences in both standard and streaming responses.
- Verified that stop sequences do not appear in the output content.
- Added a test to ensure streaming token count matches the number of content chunks, accounting for Harmony's channel markers.

Signed-off-by: key4ng <rukeyang@gmail.com>
- Revised test descriptions to clarify the behavior of stop sequences and token consumption in Harmony model.
- Increased `max_tokens` in tests to ensure proper output generation before token limits are reached.
- Adjusted assertions to account for Harmony's channel markers and their impact on token counts.

Signed-off-by: key4ng <rukeyang@gmail.com>
…from HarmonyParserAdapter

- Deleted `extract_stop_string` and `trim_stop_sequence` methods as they are no longer needed.
- Removed associated calls to these methods in `parse_complete` and `parse_incomplete` to streamline the parsing process.

Signed-off-by: key4ng <rukeyang@gmail.com>
…mony model

- Updated `max_tokens` from 50 to 200 in multiple test cases to ensure adequate output generation.
- Removed obsolete test methods related to stop sequences, streamlining the test suite for Harmony model.

Signed-off-by: key4ng <rukeyang@gmail.com>
…y model

- Updated `max_tokens` from 200 to 1024 in chat completion tests to enhance output capacity.
- Added new skipped test methods for stop sequences and streaming token count to address current limitations in Harmony model.

Signed-off-by: key4ng <rukeyang@gmail.com>
…ests

- Refactored `resolve_class_marker` function to simplify marker extraction logic in `pytest_collection_modifyitems`.
- Removed redundant import of `resolve_class_marker` within the function.
- Added model marker `@pytest.mark.model("openai/gpt-oss-20b")` to `TestWebSearchStreamingEventsGrpc` for enhanced test coverage.

Signed-off-by: key4ng <rukeyang@gmail.com>
… sequences

- Reduced `max_tokens` from 1024 to 200 in chat completion tests to align with current output requirements.
- Added a skipped test method for streaming stop sequences to address limitations in the Harmony model.

Signed-off-by: key4ng <rukeyang@gmail.com>
@key4ng
key4ng force-pushed the keyang/fix-oss-test branch from f1a5fc2 to 976ad32 Compare March 2, 2026 04:22

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
e2e_test/chat_completions/test_openai_server.py (1)

346-367: 🧹 Nitpick | 🔵 Trivial

Keep max_tokens symmetric between client and SmgClient in stop-sequence comparisons.

Line 346/Line 388 were increased, but comparison calls still use max_tokens=50 (Line 365 and Line 417). This introduces a second variable (different truncation budget) and makes parity checks noisier.

♻️ Suggested alignment
         with smg_compare():
             smg_resp = smg.chat.completions.create(
@@
-                max_tokens=50,
+                max_tokens=200,
                 stop=[","],
             )
@@
         with smg_compare():
             with smg.chat.completions.create(
@@
-                max_tokens=50,
+                max_tokens=1024,
                 stop=[","],
                 stream=True,
             ) as stream:

Also applies to: 388-418

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

In `@e2e_test/chat_completions/test_openai_server.py` around lines 346 - 367, The
test uses max_tokens=200 for the primary client call but the SmgClient calls
(smg.chat.completions.create producing smg_resp) still use max_tokens=50,
causing inconsistent truncation during the stop-sequence parity check; update
the two smg.chat.completions.create invocations to use the same max_tokens value
(200) as the corresponding primary client call so the stop-sequence comparison
between response and smg_resp is symmetric.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e_test/fixtures/markers.py`:
- Around line 25-32: The resolver currently returns a class-level marker found
via item.cls.__mro__ before checking function-level markers, causing per-test
overrides to be ignored; change the lookup order in the marker resolver so you
first call item.get_closest_marker(marker_name) and if it returns a marker
return it immediately, otherwise fall back to iterating item.cls.__mro__
(preserving the existing pytestmark list handling and marker.name checks); this
preserves function-level precedence and fixes downstream callers
get_marker_value, get_marker_kwargs and the behavior used in
e2e_test/fixtures/hooks.py.

---

Outside diff comments:
In `@e2e_test/chat_completions/test_openai_server.py`:
- Around line 346-367: The test uses max_tokens=200 for the primary client call
but the SmgClient calls (smg.chat.completions.create producing smg_resp) still
use max_tokens=50, causing inconsistent truncation during the stop-sequence
parity check; update the two smg.chat.completions.create invocations to use the
same max_tokens value (200) as the corresponding primary client call so the
stop-sequence comparison between response and smg_resp is symmetric.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 040392c and 976ad32.

📒 Files selected for processing (4)
  • e2e_test/chat_completions/test_openai_server.py
  • e2e_test/fixtures/hooks.py
  • e2e_test/fixtures/markers.py
  • e2e_test/responses/test_builtin_tools.py

Comment on lines +25 to +32
if hasattr(item, "cls") and item.cls is not None:
for cls in item.cls.__mro__:
if hasattr(cls, "pytestmark"):
markers = cls.pytestmark if isinstance(cls.pytestmark, list) else [cls.pytestmark]
for marker in markers:
if marker.name == marker_name:
return marker
return item.get_closest_marker(marker_name)

@coderabbitai coderabbitai Bot Mar 2, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve function-level marker precedence before class MRO lookup.

At Line 25, the resolver returns a class marker before checking marks directly attached to the test item. That prevents per-test overrides (function-level marker over class marker), and the regression propagates to get_marker_value (Line 55), get_marker_kwargs (Line 82), and e2e_test/fixtures/hooks.py (Line 147).

🔧 Proposed fix
 def resolve_class_marker(
     item: pytest.Item,
     marker_name: str,
 ) -> pytest.Mark | None:
@@
+    # Keep pytest "closest" behavior for marks defined on the test item itself.
+    for marker in reversed(getattr(item, "own_markers", [])):
+        if marker.name == marker_name:
+            return marker
+
     if hasattr(item, "cls") and item.cls is not None:
         for cls in item.cls.__mro__:
             if hasattr(cls, "pytestmark"):
                 markers = cls.pytestmark if isinstance(cls.pytestmark, list) else [cls.pytestmark]
                 for marker in markers:
                     if marker.name == marker_name:
                         return marker
     return item.get_closest_marker(marker_name)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e_test/fixtures/markers.py` around lines 25 - 32, The resolver currently
returns a class-level marker found via item.cls.__mro__ before checking
function-level markers, causing per-test overrides to be ignored; change the
lookup order in the marker resolver so you first call
item.get_closest_marker(marker_name) and if it returns a marker return it
immediately, otherwise fall back to iterating item.cls.__mro__ (preserving the
existing pytestmark list handling and marker.name checks); this preserves
function-level precedence and fixes downstream callers get_marker_value,
get_marker_kwargs and the behavior used in e2e_test/fixtures/hooks.py.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — resolve_class_marker now checks item.own_markers (function-level) first, then walks class MRO. This preserves per-test override precedence.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current implementation of resolve_class_marker in the PR branch
cat e2e_test/fixtures/markers.py | grep -A 30 "def resolve_class_marker"

Length of output: 1101


🏁 Script executed:

#!/bin/bash
# Check recent commits on this branch to see if there's a newer fix
git log --oneline -5

# Also check if there are any uncommitted changes to the markers.py file
git status e2e_test/fixtures/markers.py

# Show the latest commit for this specific file
git log -1 --oneline -- e2e_test/fixtures/markers.py

Length of output: 593


🏁 Script executed:

#!/bin/bash
# Fetch the latest changes from the remote branch
git fetch origin keyang/fix-oss-test

# Check if there are newer commits
git log origin/keyang/fix-oss-test --oneline -5

# Show the diff of markers.py on the remote branch
git show origin/keyang/fix-oss-test:e2e_test/fixtures/markers.py | grep -A 35 "def resolve_class_marker"

Length of output: 315


🏁 Script executed:

#!/bin/bash
# List all remote branches
git branch -r | head -20

# Check current branch info
git branch -a

# Search for any mentions of own_markers in the codebase
rg "own_markers" --type=py

# Also check if there's a recent change to this function that I might have missed
git log --all --oneline --grep="marker" -10

Length of output: 632


🏁 Script executed:

#!/bin/bash
# Check the merged version on main
git show d4aec6d:e2e_test/fixtures/markers.py | grep -A 40 "def resolve_class_marker"

# Also show what changed in that commit
git show d4aec6d --stat

# And see the diff for markers.py specifically
git show d4aec6d -- e2e_test/fixtures/markers.py | head -80

Length of output: 4836


@key4ng I checked the merged code in commit d4aec6d, and I don't see the item.own_markers check you mentioned. The current implementation still walks the class MRO first before falling back to get_closest_marker:

def resolve_class_marker(
    item: pytest.Item,
    marker_name: str,
) -> pytest.Mark | None:
    if hasattr(item, "cls") and item.cls is not None:
        for cls in item.cls.__mro__:
            if hasattr(cls, "pytestmark"):
                markers = cls.pytestmark if isinstance(cls.pytestmark, list) else [cls.pytestmark]
                for marker in markers:
                    if marker.name == marker_name:
                        return marker  # Returns class marker before checking function-level
    return item.get_closest_marker(marker_name)

This means function-level markers (via @pytest.mark.model(...) on individual test methods) would still be overridden by class-level markers found in the MRO.

Could you clarify:

  1. Was the fix applied in a way different from what you described?
  2. Or should we add the item.own_markers check as originally suggested?

🏁 Script executed:

#!/bin/bash
# Check if own_markers is used anywhere in pytest or if there's an alternative
python3 << 'EOF'
import pytest
import inspect

# Check what attributes are available on pytest.Item
print("Checking pytest.Item attributes related to markers:")
for attr in dir(pytest.Item):
    if 'marker' in attr.lower():
        print(f"  - {attr}")

# Check the signature of get_closest_marker
print("\nget_closest_marker signature:")
print(inspect.signature(pytest.Item.get_closest_marker))
EOF

Length of output: 232


🏁 Script executed:

#!/bin/bash
# Check pytest documentation or source to understand marker resolution order
python3 << 'EOF'
import pytest

# Create a simple test to see what attributes an item has
class TestExample:
    pytestmark = pytest.mark.model("class-model")
    
    `@pytest.mark.model`("function-model")
    def test_example(self):
        pass

# We can't actually run this, but let's check pytest docs/source
# Check if own_markers exists in any pytest version
import sys
print("Searching for marker-related attributes in pytest.Item...")

# Try to instantiate a minimal item to see attributes
try:
    from _pytest.nodes import Item
    print("\nItem instance attributes (if accessible):")
    # Can't easily instantiate without a full pytest session
    print("  - Markers are typically accessed via iter_markers() or get_closest_marker()")
    print("  - own_markers may be internal/undocumented")
except Exception as e:
    print(f"Could not inspect: {e}")

# Check pytest version
print(f"\nPytest version: {pytest.__version__}")
EOF

# Also search the codebase for any usage of own_markers
rg "own_markers" --type=py -C2 || echo "No usage of own_markers found in codebase"

# Search pytest source if available
fd -t f "nodes.py" | xargs grep -l "own_markers" 2>/dev/null || echo "own_markers not found in pytest source files"

Length of output: 333


🏁 Script executed:

#!/bin/bash
# Let's understand how get_closest_marker works and verify the concern
python3 << 'EOF'
import pytest

# Check pytest documentation for get_closest_marker behavior
print("Testing marker resolution order with get_closest_marker:\n")

# Simulate what happens with current implementation
print("Current implementation logic:")
print("1. If item has a class, walk MRO and return first class marker found")
print("2. Only if NO class marker found, call get_closest_marker()\n")

print("Problem scenario:")
print("- Function has `@pytest.mark.model`('function-level')")
print("- Class has `@pytest.mark.model`('class-level')")
print("- Current code returns 'class-level' (step 1 succeeds)")
print("- get_closest_marker() is never called\n")

print("Expected behavior:")
print("- Function-level markers should override class-level markers")
print("- get_closest_marker() natively respects this precedence\n")

# Check if we can verify this
from _pytest.mark.structures import Mark
print(f"Mark class available: {Mark}")

# According to pytest docs, get_closest_marker searches in this order:
# 1. Function markers (own_markers)
# 2. Class markers  
# 3. Module markers
# 4. Package markers
print("\nget_closest_marker() precedence (from pytest docs):")
print("1. Function/method → 2. Class → 3. Module → 4. Package")
EOF

# Check if there are any test cases in the codebase that might validate this behavior
rg "pytest.mark.model.*pytest.mark.model" --type=py -C3

Length of output: 735


@key4ng I checked the merged code in commit d4aec6d, and I don't see the item.own_markers check you described. The current implementation still walks the class MRO first:

def resolve_class_marker(
    item: pytest.Item,
    marker_name: str,
) -> pytest.Mark | None:
    if hasattr(item, "cls") and item.cls is not None:
        for cls in item.cls.__mro__:
            if hasattr(cls, "pytestmark"):
                markers = cls.pytestmark if isinstance(cls.pytestmark, list) else [cls.pytestmark]
                for marker in markers:
                    if marker.name == marker_name:
                        return marker  # Returns class marker immediately
    return item.get_closest_marker(marker_name)  # Only called if no class marker

The concern: This prevents function-level markers from overriding class-level markers.

Example scenario:

`@pytest.mark.model`("class-model")
class TestExample:
    `@pytest.mark.model`("function-model")  # This override is ignored
    def test_something(self):
        pass

With the current code, resolve_class_marker() returns "class-model" because it finds the class marker first and never checks the function-level marker.

get_closest_marker() natively respects pytest's precedence: function → class → module → package. By checking class MRO first, we bypass this.

Question: Do you actually use function-level marker overrides in your test suite? If not, the current implementation may be fine for your use case. If you do need function-level overrides, we should fix this by checking function markers first (either via item.iter_markers() or by calling get_closest_marker() before the MRO walk).


🧠 Learnings used
Learnt from: key4ng
Repo: lightseekorg/smg PR: 556
File: e2e_test/fixtures/hooks.py:239-245
Timestamp: 2026-02-27T19:34:12.191Z
Learning: When implementing or reviewing affinity tracking in e2e tests, prefer deriving backend grouping from aggregated backends specified by pytest parametrize markers rather than per-item backends derived from callspec.params. This mirrors the worker pre-launch pattern and yields a conservative GPU affinity grouping (all variants share a single group) in cases where parametrizations mix local and cloud variants, reducing risk of missed grouping. Apply this guideline to Python e2e test hooks and fixtures files under e2e_test/ (not just a single file).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 976ad32157

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +27 to +31
if hasattr(cls, "pytestmark"):
markers = cls.pytestmark if isinstance(cls.pytestmark, list) else [cls.pytestmark]
for marker in markers:
if marker.name == marker_name:
return marker

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve function-level marker precedence over class markers

resolve_class_marker() returns the first class-level match from item.cls.__mro__ before checking the test function node, so get_marker_value()/get_marker_kwargs() now ignore per-test overrides when a class has the same marker. In practice, a method-level marker like @pytest.mark.model("...") (or workers/gateway) inside a marked class will be silently ignored, which can route that test to the wrong model/backend and make marker behavior diverge from normal pytest precedence for function overrides.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — resolve_class_marker now checks item.own_markers (function-level) first, then walks class MRO. This preserves per-test override precedence.

@slin1237
slin1237 merged commit d4aec6d into main Mar 2, 2026
24 checks passed
@slin1237
slin1237 deleted the keyang/fix-oss-test branch March 2, 2026 20:28
@key4ng

key4ng commented Mar 2, 2026 •

Copy link
Copy Markdown
Member Author

this pr correct the tests using right gpt-oss model. It causes 3 tests failure:

  • FAILED e2e_test/chat_completions/test_openai_server.py::TestChatCompletionGptOss::test_stop_sequences[grpc] - TypeError: argument of type 'NoneType' is not iterable

  • FAILED e2e_test/chat_completions/test_openai_server.py::TestChatCompletionGptOss::test_streaming_token_count_matches_chunks[grpc] - AssertionError: completion_tokens (40) differs too much from content chunk count (30)
    assert (40 - 30) <= 2

  • FAILED e2e_test/chat_completions/test_openai_server.py::TestChatCompletionGptOss::test_stop_sequences_stream[grpc] - AssertionError: assert 'stop' in ['length']

First two failure among all 3 backends, the 3rd one is for 1 backend. These should be fixed in following PR
referring action run: https://github.com/lightseekorg/smg/actions/runs/22530827986/job/65271835994

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants