Skip to content

Conversation

@6-dehan
Copy link
Contributor

@6-dehan 6-dehan commented Jan 5, 2026

Introduces a comprehensive visual testing framework for virtio-vga driver validation using AI (Gemini) to verify screen states and display behavior.

Key features:

  • VNC environment manager with Xvfb/Xwayland support for RHEL 8-10
  • Dual screenshot capture (VNC + internal guest)
  • AI-powered visual verification for desktop state, Start Menu, BSOD detection
  • Multiple test scenarios: driver install/uninstall, resolution changes, shutdown/reboot/reset testing, driver upgrade/downgrade
  • Diagnostic capabilities to distinguish VNC vs driver issues

ID: 4900
Signed-off-by: Dehan Meng [email protected]

Summary by CodeRabbit

  • New Features

    • Added a Windows-focused visual testing framework for virtio-vga with automated driver install/update/uninstall, resolution change flows, and dual screenshot verification (host and guest).
    • Integrated host-side VNC/display environment management for remote display setup, capture, and cleanup.
  • Tests

    • Added extensive visual test scenarios, screenshot capture workflows, AI-assisted image checks, diagnostic logging, result reporting, and robust setup/teardown across host and guest.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

Walkthrough

Adds a new Windows-focused virtio-vga visual test consisting of a config file qemu/tests/cfg/virtio_vga_visual_test.cfg and a Python test module qemu/tests/virtio_vga_visual_test.py. The config defines test metadata, Windows utilities/commands, VNC/display settings, AI verification prompts, resolution workflows, and multiple test variants. The Python module introduces VNCEnvironmentManager to set up host VNC/X environments and VirtioVgaVisualTest to manage VM sessions, host VNC and guest screenshots, driver install/uninstall/upgrade flows, resolution get/set and verification, visual-quality checks (including Gemini-based checks), driver-store maintenance, and a run(test, params, env) entry point; several actions remain unimplemented stubs.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding a virtio-vga visual test framework with AI-based (Gemini) verification capabilities, which is the primary focus of both the new config file and test implementation.
Docstring Coverage ✅ Passed Docstring coverage is 83.64% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link

@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

Fix all issues with AI Agents 🤖
In @qemu/tests/virtio_vga_visual_test.py:
- Around line 423-425: The code assumes delete_file_cmd is present and does
self.params.get("delete_file_cmd") % guest_img_path which raises a TypeError if
the param is missing; update the logic in the block using delete_cmd (the
variable assigned from self.params.get("delete_file_cmd")) to validate existence
before formatting and executing: fetch the param into delete_cmd, check if
delete_cmd is truthy (or provide a sensible default/skip behavior), only perform
the % guest_img_path interpolation when delete_cmd is a string, and then call
self.session.cmd_status(delete_cmd, timeout=30) (or skip/log a warning when the
param is absent) so the code does not attempt string formatting on None.
- Around line 1225-1228: The reboot call inside run_reset_with_driver is missing
the session argument which breaks session tracking; update the vm.reboot(...)
call in the reset loop to pass the current session (use session=self.session as
done in run_reboot_with_driver) so the reboot reuses/tracks the correct session
and then continue appending self.session to self._open_session_list.
🧹 Nitpick comments (7)
qemu/tests/cfg/virtio_vga_visual_test.cfg (1)

46-46: Internal URL may not be accessible in all environments.

The gvncviewer_tool_url points to an internal Red Hat server (download.eng.pek2.redhat.com). This could cause test failures in environments without access to this internal network.

Consider making this URL configurable or providing a fallback mechanism.

qemu/tests/virtio_vga_visual_test.py (6)

61-77: Silent exception swallowing may hide configuration issues.

The broad except Exception block silently ignores any errors when reading /etc/redhat-release. If the file exists but has unexpected content or permissions issues, the test will silently fall back to RHEL 9, which could lead to incorrect display server selection.

Consider logging the exception at debug level to aid troubleshooting.

🔎 Proposed fix
         except Exception:
-            pass
+            LOG_JOB.debug("Failed to detect RHEL version, defaulting to 9")
 
         # Default to RHEL 9
         self.rhel_version = 9

110-138: Lock file presence doesn't guarantee Xvfb is running.

Checking only for the lock file existence (line 117) doesn't verify the Xvfb process is actually running. The lock file could be stale from a crashed process. Consider verifying the process is alive before returning success.

🔎 Proposed fix
         # Check if display already running by checking lock file
         lock_file = "/tmp/.X%s-lock" % self.display_num
         if os.path.exists(lock_file):
+            # Verify the process is actually running
+            try:
+                with open(lock_file, "r") as f:
+                    pid = int(f.read().strip())
+                os.kill(pid, 0)  # Check if process exists
+            except (ValueError, OSError, ProcessLookupError):
+                # Stale lock file, remove it
+                LOG_JOB.warning("Removing stale Xvfb lock file: %s", lock_file)
+                os.remove(lock_file)
+            else:
+                os.environ["DISPLAY"] = display
+                return True
-            os.environ["DISPLAY"] = display
-            return True

297-309: VNC port calculation assumes standard base port.

The calculation vnc_display = vnc_port - 5900 assumes VNC always uses the standard base port. While this is typically correct, if QEMU uses a non-standard VNC configuration, this could produce incorrect display numbers.


348-354: Hard-coded screenshot path reduces flexibility.

The default screenshot path /home/desktop.png is hard-coded. If the gvncviewer-customize tool changes its output location or if multiple concurrent tests run, this could cause issues.


836-845: Using getattr on VM object with potentially undefined attribute.

getattr(self.vm, "REBOOT_TIMEOUT", 240) retrieves a class/instance attribute, but REBOOT_TIMEOUT is likely meant to be a param, not a VM attribute. Consider using self.params.get("reboot_timeout", 240) for consistency with other timeout handling in this file.

🔎 Proposed fix
     def _wait_for_shutdown(self):
         """Wait for VM to shutdown, then restart."""
         error_context.context("Waiting for VM to shutdown", LOG_JOB.info)
-        timeout = getattr(self.vm, "REBOOT_TIMEOUT", 240)
+        timeout = int(self.params.get("reboot_timeout", 240))
         if not utils_misc.wait_for(self.vm.is_dead, timeout=timeout):
             self.test.fail("VM failed to shutdown within %d seconds" % timeout)

1013-1028: Document the meaning of Windows exit status codes.

Status codes 3010 (ERROR_SUCCESS_REBOOT_REQUIRED) and 259 (STILL_ACTIVE) have specific meanings in Windows. Adding a brief comment would improve code clarity.

🔎 Proposed fix
+                # 0: Success
+                # 3010: ERROR_SUCCESS_REBOOT_REQUIRED
+                # 259: STILL_ACTIVE (operation pending)
                 if status in (0, 3010, 259):
                     removed_count += 1
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa12759 and d9a25cd.

📒 Files selected for processing (2)
  • qemu/tests/cfg/virtio_vga_visual_test.cfg
  • qemu/tests/virtio_vga_visual_test.py
🧰 Additional context used
🧬 Code graph analysis (1)
qemu/tests/virtio_vga_visual_test.py (1)
provider/win_driver_utils.py (1)
  • install_driver_by_virtio_media (159-194)
🔇 Additional comments (8)
qemu/tests/cfg/virtio_vga_visual_test.cfg (2)

1-19: LGTM!

The test metadata, driver configuration, and devcon path setup are well-structured with appropriate architecture-specific handling for i386/x86_64.


52-95: LGTM!

The test variants are well-organized, covering a comprehensive set of scenarios including basic interaction, shutdown/reboot/reset cycles, driver lifecycle tests, and resolution modifications.

qemu/tests/virtio_vga_visual_test.py (6)

1-13: LGTM!

Imports are appropriate for the module's functionality, and the logger is correctly configured using the avocado test logger.


194-221: RHEL 10+ setup path does not start the display server.

For RHEL 10+, install_xwayland() is called but there's no corresponding start_xwayland() method or call. The _vnc_screendump method (lines 314-327) wraps commands with xwfb-run for RHEL 10+, but the display environment may not be properly initialized before tool installation.

Verify this is intentional and that xwfb-run handles display initialization on-demand.


571-582: Key sending logic executes after command completion, not during.

The key_to_install_driver mechanism sends keys after cmd_status_output returns with a non-zero status. However, if the command is waiting for user interaction, it would timeout or fail before reaching this code. This logic appears to be intended for handling driver installation prompts, but the sequencing seems incorrect.

Verify this pattern matches the expected behavior for handling Windows driver installation dialogs.


1369-1388: Stub methods correctly fail with descriptive messages.

The not-yet-implemented test actions properly call self.test.fail() with clear messages indicating they're not implemented. This is the appropriate pattern for work-in-progress test scaffolding.


1528-1559: LGTM!

The main run() method has a clean structure with proper error handling via try/finally for cleanup, dynamic test dispatch using getattr, and appropriate VNC environment initialization gating.


1562-1566: LGTM!

The entry point function correctly instantiates the test class and delegates to the run() method.

Copy link

@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

Fix all issues with AI Agents 🤖
In @qemu/tests/virtio_vga_visual_test.py:
- Around line 99-108: Typo: the code checks for and invokes "xwfb-run" but
should use "xwayland-run". In install_xwayland(), replace the check that runs
which xwfb-run with which xwayland-run so the detected command matches the
package being installed, and likewise update the later invocation (the call that
runs xwfb-run around line 318) to invoke xwayland-run; update any related
_run_cmd calls and string literals that set or rely on the command name (e.g.,
in install_xwayland and the runtime invocation) so both checks and executions
consistently use "xwayland-run".
- Around line 677-687: Add an upfront Gemini API availability check (either in
_ensure_vnc_environment() or at the start of run()) so
verify_screen_with_gemini() calls (used around response handling and diagnostics
in this file) fail fast with a clear diagnostic; implement by invoking a
lightweight ppm_utils API-check helper (or add one if missing) that validates
credentials and endpoint reachability, and if it fails raise a clear
TestFail/RuntimeError with an explanatory message so subsequent calls to
ppm_utils.verify_screen_with_gemini() (used at the primary checks and
diagnostics) are not attempted when Gemini is misconfigured or unreachable.
♻️ Duplicate comments (2)
qemu/tests/virtio_vga_visual_test.py (2)

423-425: Potential TypeError if delete_file_cmd parameter is missing.

This issue was previously identified. If delete_file_cmd is not defined in params, the % formatting will raise a TypeError.

🔎 Proposed fix
-            delete_cmd = self.params.get("delete_file_cmd") % guest_img_path
-            self.session.cmd_status(delete_cmd, timeout=30)
+            delete_cmd_template = self.params.get("delete_file_cmd")
+            if delete_cmd_template:
+                delete_cmd = delete_cmd_template % guest_img_path
+                self.session.cmd_status(delete_cmd, timeout=30)

1225-1228: Inconsistent reboot call - missing session parameter.

This issue was previously identified. The vm.reboot() call doesn't pass the session parameter, unlike similar calls in other methods (e.g., line 1200-1201). This breaks session tracking.

🔎 Proposed fix
         for i in range(5):
             error_context.context("Reset loop %d/5" % (i + 1), LOG_JOB.info)
-            self.session = self.vm.reboot(method="system_reset", timeout=reboot_timeout)
+            self.session = self.vm.reboot(
+                session=self.session, method="system_reset", timeout=reboot_timeout
+            )
             self._open_session_list.append(self.session)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9a25cd and f003476.

📒 Files selected for processing (2)
  • qemu/tests/cfg/virtio_vga_visual_test.cfg
  • qemu/tests/virtio_vga_visual_test.py
🧰 Additional context used
🧬 Code graph analysis (1)
qemu/tests/virtio_vga_visual_test.py (1)
provider/win_driver_utils.py (1)
  • install_driver_by_virtio_media (159-194)
🔇 Additional comments (5)
qemu/tests/cfg/virtio_vga_visual_test.cfg (1)

1-94: Configuration structure looks solid.

The test configuration is well-organized with appropriate timeouts, Windows guest commands, AI verification prompts, and comprehensive test variants. The WIN_UTILS placeholder approach for dynamic path resolution is a good design choice.

qemu/tests/virtio_vga_visual_test.py (4)

38-59: Safe subprocess usage pattern.

The _run_cmd method properly handles both shell and non-shell command execution with appropriate timeout and error handling. The conditional shell usage is controlled and safe for this test framework context.


483-616: Well-structured driver update implementation.

The _update_driver method handles both install and upgrade scenarios appropriately, with proper INF discovery, version tracking, and verification. Good handling of devcon command statuses (0, 1) and driver verification via driverquery.


936-1051: Robust driver store cleanup with regex-based parsing.

The _clean_driver_store method uses sophisticated regex parsing to identify driver packages and handles both Windows 2016 and newer versions correctly. The dual approach (PnPUtil + WMIC fallback) increases reliability.


1396-1505: Comprehensive resolution verification with multi-layer checks.

The manual resolution modification test properly validates resolution changes through three mechanisms: Windows API queries, VNC screenshot dimension verification (with reasonable 10px tolerance), and AI-powered visual quality checks. This multi-layer approach effectively catches different types of failures.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In @qemu/tests/virtio_vga_visual_test.py:
- Around line 572-579: The current check formats chk_cmd with a truncated
device_name (device_name[0:30]) which can produce false positives; update the
formatting to use the full device_name when building chk_cmd (keep chk_cmd,
device_name, session.cmd_status, wait_for, and driver_name as the reference
points) and, if needed, make the match more specific (for example use an
exact/quoted match or a more strict driverquery/find pattern) so the wait_for
result reliably targets the intended driver rather than a prefix match.
🧹 Nitpick comments (7)
qemu/tests/cfg/virtio_vga_visual_test.cfg (1)

52-94: Test variants are comprehensive.

The variants cover essential driver lifecycle scenarios. Note that several variants (check_driver_signature_signtool, check_driver_signature_sigverif, boot_multiple_drivers, install_driver_iommu, desktop_migration, etc.) are defined here but will fail with "not yet implemented" errors per the Python implementation. Consider either:

  • Adding a TODO comment noting which variants are stubs
  • Temporarily commenting out unimplemented variants to prevent accidental CI failures
qemu/tests/virtio_vga_visual_test.py (6)

65-81: Silent exception swallowing hides diagnostic information.

The bare except Exception: pass (lines 76-77) will silently hide any errors during RHEL version detection. Consider logging the exception for debugging purposes.

Proposed fix
         try:
             if os.path.exists("/etc/redhat-release"):
                 with open("/etc/redhat-release", "r") as f:
                     content = f.read()
                 match = re.search(r"release\s+(\d+)", content, re.IGNORECASE)
                 if match:
                     version = int(match.group(1))
                     self.rhel_version = version
                     return version
-        except Exception:
-            pass
+        except Exception as e:
+            self.log(f"Failed to detect RHEL version: {e}")
 
         # Default to RHEL 9
         self.rhel_version = 9
         return 9

343-348: Hardcoded screenshot path creates fragile dependency.

The path /home/desktop.png is hardcoded assuming the gvncviewer-customize tool always saves to this location. Consider parameterizing this or documenting the dependency on tool behavior.

Proposed improvement
+            # gvncviewer-customize saves to /home/desktop.png by default
+            # This path is determined by the tool's implementation
+            default_screenshot = self.params.get(
+                "gvncviewer_screenshot_path", "/home/desktop.png"
+            )
-            # gvncviewer-customize saves to /home/desktop.png by default
-            default_screenshot = "/home/desktop.png"

712-713: Hardcoded settle time could be parameterized.

The 5-second settle time is hardcoded. Consider making this configurable via params for different guest performance characteristics.

Proposed improvement
-        settle_time = 5
-        time.sleep(settle_time)
+        settle_time = int(self.params.get("visual_settle_time", 5))
+        time.sleep(settle_time)

816-825: Method name is misleading - it also restarts the VM.

The method _wait_for_shutdown not only waits for shutdown but also restarts the VM (line 824). Consider renaming to _wait_for_shutdown_and_restart or _perform_powerdown_cycle to accurately reflect its behavior.


1333-1357: Use test.error() or test.cancel() instead of test.fail() for unimplemented tests.

The stub methods use self.test.fail() for not-yet-implemented functionality. Semantically:

  • test.fail() = test ran but assertions failed (bug found)
  • test.error() = test could not run due to environment/setup issues
  • test.cancel() = test intentionally skipped

For unimplemented features, test.cancel() with a message or test.error() would be more appropriate to distinguish from actual test failures.

Proposed fix (example for one method)
     @error_context.context_aware
     def run_check_driver_signature_signtool(self):
-        self.test.fail(
+        self.test.cancel(
             "Test action 'check_driver_signature_signtool' is not yet implemented."
         )

1168-1171: Session list grows unboundedly during reboot loops.

Each reboot appends a new session to _open_session_list without removing the old one. While _cleanup_open_session handles this correctly at the end, during long-running tests with many reboots, the list accumulates stale session references. Consider closing the old session before appending the new one, or clearing the list after successful reboot.

Proposed improvement
         for i in range(5):
             error_context.context("Reboot loop %d/5" % (i + 1), LOG_JOB.info)
+            # Close current session before reboot
+            if self.session:
+                try:
+                    self.session.close()
+                except Exception:
+                    pass
             self.session = self.vm.reboot(
                 session=self.session, method="shell", timeout=reboot_timeout
             )
             self._open_session_list.append(self.session)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f003476 and 7690aca.

📒 Files selected for processing (2)
  • qemu/tests/cfg/virtio_vga_visual_test.cfg
  • qemu/tests/virtio_vga_visual_test.py
🧰 Additional context used
🧬 Code graph analysis (1)
qemu/tests/virtio_vga_visual_test.py (1)
provider/win_driver_utils.py (1)
  • install_driver_by_virtio_media (159-194)
🔇 Additional comments (8)
qemu/tests/cfg/virtio_vga_visual_test.cfg (1)

1-51: Configuration looks well-structured.

The test configuration is comprehensive with appropriate timeouts, command templates, and AI prompts. A few observations:

  1. Line 46: The gvncviewer_tool_url points to an internal Red Hat server. Ensure this URL is accessible from the test environment and consider documenting fallback options for external contributors.

  2. Line 11: The device_hwid uses a specific hardware ID with revision. Verify this matches all supported virtio-vga device variants.

qemu/tests/virtio_vga_visual_test.py (7)

424-471: LGTM - Dual verification approach is well-designed.

The method correctly implements a fallback strategy: try VNC first, always capture internal screenshot as diagnostic backup, and clearly differentiates between VNC transmission issues and actual driver problems. The return tuple provides all necessary context for callers.


936-948: Regex parsing for driver blocks looks correct.

The regex pattern correctly handles both Windows 2016 and newer pnputil output formats. The use of re.DOTALL ensures multi-line block matching works properly.


1424-1448: Good multi-layered verification approach for resolution testing.

The implementation correctly verifies resolution through three independent methods:

  1. Command-line query from guest OS
  2. Screenshot file dimensions analysis
  3. AI-based visual quality check

The 10-pixel tolerance (line 1436) is reasonable for minor rendering differences.


1497-1527: Clean dispatcher pattern for test execution.

The dynamic method dispatch using getattr is a clean approach that scales well with new test types. The try/finally block ensures session cleanup even on failures.


879-890: LGTM - Clean path resolution utility.

The _resolve_winutils_path method correctly handles the WIN_UTILS: placeholder resolution with appropriate error handling when the drive letter cannot be detected.


1206-1250: Well-designed install/uninstall test workflow.

The test correctly:

  1. Ejects the virtio ISO before uninstalling to prevent auto-reinstall
  2. Verifies the guest state after uninstallation
  3. Re-inserts the ISO and reinstalls
  4. Includes reboots at appropriate points

This ensures proper test isolation and verification at each stage.


103-112: No issue found. The xwfb-run binary check is correct—it is one of the binaries provided by the xwayland-run package (along with xwayland-run and wlheadless-run). The code logic is sound: it checks if xwfb-run is available, and if not, installs the xwayland-run package to provide it.

Likely an incorrect or invalid review comment.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In `@qemu/tests/virtio_vga_visual_test.py`:
- Around line 676-690: The Gemini yes/no parsing using response.lower().split()
can miss punctuation like "NO." or "YES,", so update the checks that examine
response and internal_response in virtio_vga_visual_test.py (the blocks around
prompt_desktop, response = ppm_utils.verify_screen_with_gemini and
internal_response = ppm_utils.verify_screen_with_gemini) to normalize and
robustly test for whole-word yes/no (e.g., lowercase the string, strip
surrounding punctuation, or use a word-boundary regex like r'\b(?:yes|no)\b' to
search for matches) and apply the same change to the other occurrences noted
(around the blocks at ~760-788 and ~1493-1499) so punctuation-wrapped answers
are recognized correctly.
- Around line 1074-1091: wmic.parse_list() can return a string which will make
the for loop iterate characters; before iterating inf_names (the variable from
wmic.parse_list) add the same defensive type check used earlier: detect if
inf_names is a str and convert it into a single-element list (or an empty list
if it's falsy) so the subsequent loop over pnputil_delete_cmd and the
session.cmd_status_output calls receive whole INF names rather than characters;
update the block that sets inf_names and before the for inf_name in inf_names
loop perform this type normalization.
- Around line 140-191: The download_tool currently fetches gvncviewer from an
HTTP URL (tool_url) without integrity checks, creating a supply-chain risk;
update download_tool to require or accept a new gvncviewer_tool_sha256 test
parameter and after download compute the file's SHA256 (using
tool_download_path) and compare it to the provided value, raising RuntimeError
on mismatch, and also reject non-HTTPS URLs (tool_url) unless a checksum is
provided; update install_tool to rely on the verified download and ensure the
same symbols (download_tool, install_tool, tool_url, tool_download_path,
tool_install_path, gvncviewer_tool_sha256) are used so existing flows call the
verified path.

Comment on lines +140 to +191
def download_tool(self):
"""Download gvncviewer-customize from server."""
if not self.tool_url:
raise ValueError("gvncviewer_tool_url not specified in test parameters")

# Remove old download
if os.path.exists(self.tool_download_path):
os.remove(self.tool_download_path)

# Use curl or wget
rc, _, _ = self._run_cmd(["which", "curl"], check=False)
if rc == 0:
self._run_cmd(
["curl", "-L", "-o", self.tool_download_path, self.tool_url],
timeout=120,
)
else:
self._run_cmd(
["wget", "-O", self.tool_download_path, self.tool_url], timeout=120
)

# Verify download
if (
not os.path.exists(self.tool_download_path)
or os.path.getsize(self.tool_download_path) == 0
):
raise RuntimeError("Tool download failed")

return self.tool_download_path

def install_tool(self):
"""Install gvncviewer-customize to /usr/bin."""
# Check if already installed and executable
if os.path.exists(self.tool_install_path) and os.access(
self.tool_install_path, os.X_OK
):
return True

# Download if needed
if not os.path.exists(self.tool_download_path):
self.download_tool()

# Make executable and move to /usr/bin
os.chmod(self.tool_download_path, 0o755)
self._run_cmd(["mv", self.tool_download_path, self.tool_install_path])

# Verify installation
if not os.path.exists(self.tool_install_path) or not os.access(
self.tool_install_path, os.X_OK
):
raise RuntimeError("Tool installation failed")

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for existing checksum/verification patterns and gvncviewer references
rg -n --type=py --type=cfg 'sha256|checksum|verify.*download|gvncviewer' qemu/tests

Repository: autotest/tp-qemu

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Search for tool_url and tool_download_path patterns
rg -n 'tool_url|tool_download_path|tool_install_path' qemu/tests --type=py

Repository: autotest/tp-qemu

Length of output: 2131


🏁 Script executed:

#!/bin/bash
# Check if params are used for tool configuration elsewhere
rg -n 'self\.params\.get.*tool|self\.params.*download' qemu/tests --type=py -A 2

Repository: autotest/tp-qemu

Length of output: 384


🏁 Script executed:

#!/bin/bash
# Look for HTTPS enforcement or SSL/TLS patterns in downloads
rg -n 'https://|http://|verify.*cert|ssl|tls' qemu/tests --type=py | head -30

Repository: autotest/tp-qemu

Length of output: 2841


🏁 Script executed:

#!/bin/bash
# Check the full context around tool initialization and params
sed -n '1,50p' qemu/tests/virtio_vga_visual_test.py

Repository: autotest/tp-qemu

Length of output: 1587


🏁 Script executed:

#!/bin/bash
# Look for any documentation or comments about gvncviewer_tool_url in the repo
rg -n 'gvncviewer_tool_url' -B 3 -A 3 qemu/tests --type=py

Repository: autotest/tp-qemu

Length of output: 1180


🏁 Script executed:

#!/bin/bash
# Check if there's any documentation or test config files mentioning this
find qemu -name "*.cfg" -o -name "*.conf" -o -name "README*" -o -name "*.md" | xargs grep -l "gvncviewer" 2>/dev/null || echo "No config files found"

Repository: autotest/tp-qemu

Length of output: 101


🏁 Script executed:

#!/bin/bash
# Check if there's any checksum/hash validation patterns used in downloads anywhere in the codebase
rg -n 'hashlib|sha256|sha1|md5' qemu/tests --type=py | head -20

Repository: autotest/tp-qemu

Length of output: 1972


🏁 Script executed:

#!/bin/bash
# Check the test config file for parameter documentation
cat qemu/tests/cfg/virtio_vga_visual_test.cfg

Repository: autotest/tp-qemu

Length of output: 4991


🏁 Script executed:

#!/bin/bash
# Check if other similar test files use checksum parameters
rg -n 'tool_sha256|tool_checksum|download.*sha256' qemu/tests --type=py

Repository: autotest/tp-qemu

Length of output: 42


Add integrity verification for the downloaded VNC tool.
The executable is downloaded from an HTTP URL (http://download.eng.pek2.redhat.com/...) without checksum or signature validation, creating a supply-chain risk. Add a checksum parameter and either require it or enforce HTTPS.

Suggested hardening (checksum + non-HTTPS guard)
+import hashlib
+
 def download_tool(self):
     """Download gvncviewer-customize from server."""
     if not self.tool_url:
         raise ValueError("gvncviewer_tool_url not specified in test parameters")
@@
     # Verify download
     if (
         not os.path.exists(self.tool_download_path)
         or os.path.getsize(self.tool_download_path) == 0
     ):
         raise RuntimeError("Tool download failed")
+
+    expected_sha256 = self.params.get("gvncviewer_tool_sha256")
+    if expected_sha256:
+        with open(self.tool_download_path, "rb") as f:
+            actual = hashlib.sha256(f.read()).hexdigest()
+        if actual.lower() != expected_sha256.lower():
+            raise RuntimeError("Tool checksum mismatch")
+    elif not self.tool_url.startswith("https://"):
+        raise RuntimeError("Refusing non-HTTPS download without checksum")

Obtain or compute the SHA256 of the gvncviewer-customize tool and add gvncviewer_tool_sha256 to the test config, or migrate the URL to HTTPS.

🤖 Prompt for AI Agents
In `@qemu/tests/virtio_vga_visual_test.py` around lines 140 - 191, The
download_tool currently fetches gvncviewer from an HTTP URL (tool_url) without
integrity checks, creating a supply-chain risk; update download_tool to require
or accept a new gvncviewer_tool_sha256 test parameter and after download compute
the file's SHA256 (using tool_download_path) and compare it to the provided
value, raising RuntimeError on mismatch, and also reject non-HTTPS URLs
(tool_url) unless a checksum is provided; update install_tool to rely on the
verified download and ensure the same symbols (download_tool, install_tool,
tool_url, tool_download_path, tool_install_path, gvncviewer_tool_sha256) are
used so existing flows call the verified path.

Comment on lines +676 to +690
prompt_desktop = self.params.get("prompt_desktop_check")
response = ppm_utils.verify_screen_with_gemini(
screenshot_path, prompt_desktop, results_dir=self.results_dir
)

if "no" in response.lower().split():
# Primary verification failed, need to diagnose
if vnc_success and internal_success:
# VNC verification failed, check Internal as diagnostic
internal_response = ppm_utils.verify_screen_with_gemini(
internal_path, prompt_desktop, results_dir=self.results_dir
)

if "yes" in internal_response.lower().split():
# Internal is OK, VNC has display issue - ERROR
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize Gemini yes/no parsing to handle punctuation.
response.lower().split() misses values like NO. or YES,, which can silently misclassify results. Use a word‑boundary regex for robust parsing.

✅ Robust yes/no parsing
-        if "no" in response.lower().split():
+        if re.search(r"\bno\b", response, re.I):
@@
-                if "yes" in internal_response.lower().split():
+                if re.search(r"\byes\b", internal_response, re.I):
@@
-            if "yes" in resp.lower().split():
+            if re.search(r"\byes\b", resp, re.I):
@@
-                if "yes" in internal_response.lower().split():
+                if re.search(r"\byes\b", internal_response, re.I):
@@
-            if "no" in ai_response.lower().split():
+            if re.search(r"\bno\b", ai_response, re.I):

Also applies to: 760-788, 1493-1499

🤖 Prompt for AI Agents
In `@qemu/tests/virtio_vga_visual_test.py` around lines 676 - 690, The Gemini
yes/no parsing using response.lower().split() can miss punctuation like "NO." or
"YES,", so update the checks that examine response and internal_response in
virtio_vga_visual_test.py (the blocks around prompt_desktop, response =
ppm_utils.verify_screen_with_gemini and internal_response =
ppm_utils.verify_screen_with_gemini) to normalize and robustly test for
whole-word yes/no (e.g., lowercase the string, strip surrounding punctuation, or
use a word-boundary regex like r'\b(?:yes|no)\b' to search for matches) and
apply the same change to the other occurrences noted (around the blocks at
~760-788 and ~1493-1499) so punctuation-wrapped answers are recognized
correctly.

Comment on lines +1074 to +1175
output = self.session.cmd(cmd, timeout=360)
inf_names = wmic.parse_list(output)

if not inf_names:
LOG_JOB.warning("No driver packages found for '%s'", device_name)
else:
LOG_JOB.info("Uninstalling %d driver package(s)", len(inf_names))

cmd_key = (
"pnputil_delete_cmd_win2016" if self._is_win2016() else "pnputil_delete_cmd"
)
pnputil_delete_cmd = self.params.get(cmd_key)

for inf_name in inf_names:
status, output = self.session.cmd_status_output(
pnputil_delete_cmd % inf_name, inst_timeout
)
if status not in (0, 3010):
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find wmic module and parse_list function
find . -name "*wmic*.py" -type f | head -20

Repository: autotest/tp-qemu

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for parse_list definition
rg -n "def parse_list" --type=py

Repository: autotest/tp-qemu

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Get context around parse_list implementation
rg -n -A 20 "def parse_list" --type=py

Repository: autotest/tp-qemu

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for parse_list across all Python files
rg -n "parse_list" --type=py -A 5

Repository: autotest/tp-qemu

Length of output: 2926


🏁 Script executed:

#!/bin/bash
# Look at the imports and structure of virtio_vga_visual_test.py
head -100 qemu/tests/virtio_vga_visual_test.py | grep -E "^import|^from|wmic"

Repository: autotest/tp-qemu

Length of output: 284


🏁 Script executed:

#!/bin/bash
# Search for wmic import or usage
rg -n "wmic" --type=py -B 2 -A 2

Repository: autotest/tp-qemu

Length of output: 24711


🏁 Script executed:

#!/bin/bash
# Find the wmic module in virttest
find . -path "*/virttest/utils_windows*" -name "*wmic*" -type f

Repository: autotest/tp-qemu

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for parse_list in virttest utilities
rg -n "def parse_list" --type=py

Repository: autotest/tp-qemu

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Look for utils_windows directory
find . -type d -name "utils_windows"

Repository: autotest/tp-qemu

Length of output: 42


Add type validation for wmic.parse_list() output before iteration.

The code at lines 986-988 in this same file already includes a defensive check for this scenario. Apply the same pattern here: if parse_list() returns a string instead of a list, the for loop will iterate character-by-character, passing invalid inf names to pnputil.

Suggested fix
        inf_names = wmic.parse_list(output)

        if not inf_names:
            LOG_JOB.warning("No driver packages found for '%s'", device_name)
         else:
+            if not isinstance(inf_names, list):
+                inf_names = [inf_names]
             LOG_JOB.info("Uninstalling %d driver package(s)", len(inf_names))
🤖 Prompt for AI Agents
In `@qemu/tests/virtio_vga_visual_test.py` around lines 1074 - 1091,
wmic.parse_list() can return a string which will make the for loop iterate
characters; before iterating inf_names (the variable from wmic.parse_list) add
the same defensive type check used earlier: detect if inf_names is a str and
convert it into a single-element list (or an empty list if it's falsy) so the
subsequent loop over pnputil_delete_cmd and the session.cmd_status_output calls
receive whole INF names rather than characters; update the block that sets
inf_names and before the for inf_name in inf_names loop perform this type
normalization.

@6-dehan 6-dehan force-pushed the gpu-AI-image branch 2 times, most recently from c28d5f9 to f52cbc5 Compare January 19, 2026 09:45
Copy link

@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

🤖 Fix all issues with AI agents
In `@qemu/tests/virtio_vga_visual_test.py`:
- Around line 115-119: The current check treats any existing lock_file
("/tmp/.X%s-lock" % self.display_num) as proof the display is running; instead,
read the PID from that lock_file (lock_file), verify the process is alive (e.g.,
os.kill(pid, 0) or equivalent) and only then set os.environ["DISPLAY"]=display
and return True; if the PID is not running or the file contains invalid data,
remove the stale lock_file and continue (do not return True). Update the logic
in the method that uses self.display_num and display to perform this liveness
check and cleanup before assuming the display is active.
♻️ Duplicate comments (9)
qemu/tests/virtio_vga_visual_test.py (9)

99-108: Typo in xwayland command check (previously flagged).

Line 101 checks for xwfb-run but should check for xwayland-run to match the installed package.


140-168: Supply-chain risk: No checksum verification for downloaded tool (previously flagged).

The download lacks integrity verification. Previous review suggested adding SHA256 checksum validation.


283-295: VNC environment setup with idempotent pattern (Gemini validation previously flagged).

The one-time setup pattern is correct. A previous review suggested adding early Gemini API validation here to fail fast if credentials are misconfigured.


314-327: Malformed xwayland command construction (previously flagged).

The -s flag syntax and -geometry option usage were flagged in a previous review as invalid for xwfb-run/mutter.


423-425: Potential TypeError if delete_file_cmd is missing (previously flagged).

If delete_file_cmd is not in params, the % formatting will raise TypeError on None.


589-596: Device name truncation may cause false positives (previously flagged).

The check uses device_name[0:30] which could match wrong drivers if they share a prefix.


681-689: Gemini yes/no parsing may miss punctuated responses (previously flagged).

Using response.lower().split() won't match "NO." or "YES," due to punctuation. A previous review suggested using word-boundary regex.


1074-1091: Missing type validation for wmic.parse_list() output (previously flagged).

Unlike _clean_driver_store (which has the fix at lines 986-988), this method doesn't validate that inf_names is a list before iterating.


1225-1228: Missing session parameter in reboot call (previously flagged).

The vm.reboot() call here doesn't pass session=self.session unlike the similar call in run_reboot_with_driver at line 1200-1201.

🧹 Nitpick comments (3)
qemu/tests/virtio_vga_visual_test.py (3)

61-77: Consider logging when defaulting to RHEL version.

The fallback to RHEL 9 is reasonable, but silently defaulting could mask detection issues. Consider adding a debug log when the default is used.

Suggested improvement
         except Exception:
             pass
 
         # Default to RHEL 9
         self.rhel_version = 9
+        LOG_JOB.debug("Could not detect RHEL version, defaulting to 9")
         return 9

348-354: Hardcoded screenshot path reduces flexibility.

The default screenshot path /home/desktop.png is hardcoded. Consider making this configurable via params or deriving it from the tool's behavior, in case the tool's default changes or varies by version.

Suggested improvement
+            # gvncviewer-customize saves to /home/desktop.png by default
+            default_screenshot = self.params.get(
+                "gvncviewer_default_output", "/home/desktop.png"
+            )
-            # gvncviewer-customize saves to /home/desktop.png by default
-            default_screenshot = "/home/desktop.png"

1369-1393: Consider using test.cancel() for unimplemented stubs.

Using test.fail() for unimplemented test actions marks them as failures. Consider using test.cancel() (if available) or a skip mechanism to indicate these are planned but not yet implemented, rather than actual test failures.

Suggested change (example)
     `@error_context.context_aware`
     def run_check_driver_signature_signtool(self):
-        self.test.fail(
+        self.test.cancel(
             "Test action 'check_driver_signature_signtool' is not yet implemented."
         )

Comment on lines +115 to +119
# Check if display already running by checking lock file
lock_file = "/tmp/.X%s-lock" % self.display_num
if os.path.exists(lock_file):
os.environ["DISPLAY"] = display
return True
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the file structure and search for process/pid verification patterns
rg -n "proc|pid|stale|lock" qemu/tests/virtio_vga_visual_test.py -A 2 -B 2

Repository: autotest/tp-qemu

Length of output: 3293


🏁 Script executed:

# Get the full start_xvfb method to see current implementation
sed -n '110,138p' qemu/tests/virtio_vga_visual_test.py

Repository: autotest/tp-qemu

Length of output: 1058


🏁 Script executed:

# Check git log for commit c28d5f9 or related commits mentioning lock or stale
git log --oneline --all --grep="lock\|stale" 2>/dev/null | head -20

Repository: autotest/tp-qemu

Length of output: 42


🏁 Script executed:

# Search for any process checking logic in the file
ast-grep --pattern 'Popen|subprocess|psutil|os.kill|os.path.isfile' qemu/tests/virtio_vga_visual_test.py

Repository: autotest/tp-qemu

Length of output: 42


Address stale lock file vulnerability in display check.

The code at lines 117-119 assumes a lock file's existence means the display is running, but does not verify the process is actually alive. This can cause silent failures if the lock file persists after an Xvfb process crashes. The previous review reference to commit c28d5f9 cannot be found in the repository history. Add process liveness verification when a lock file exists—either by checking if the process is still running or by attempting to verify display connectivity before returning True.

🤖 Prompt for AI Agents
In `@qemu/tests/virtio_vga_visual_test.py` around lines 115 - 119, The current
check treats any existing lock_file ("/tmp/.X%s-lock" % self.display_num) as
proof the display is running; instead, read the PID from that lock_file
(lock_file), verify the process is alive (e.g., os.kill(pid, 0) or equivalent)
and only then set os.environ["DISPLAY"]=display and return True; if the PID is
not running or the file contains invalid data, remove the stale lock_file and
continue (do not return True). Update the logic in the method that uses
self.display_num and display to perform this liveness check and cleanup before
assuming the display is active.

Introduces a comprehensive visual testing framework for virtio-vga driver
validation using AI (Gemini) to verify screen states and display behavior.

Key features:
- VNC environment manager with Xvfb/Xwayland support for RHEL 8-10
- Dual screenshot capture (VNC + internal guest)
- AI-powered visual verification for desktop state, Start Menu, BSOD detection
- Multiple test scenarios: driver install/uninstall, resolution changes,
  shutdown/reboot/reset testing, driver upgrade/downgrade
- Diagnostic capabilities to distinguish VNC vs driver issues

Signed-off-by: Dehan Meng <[email protected]>
add 'virtio_vga_visual_test.check_driver_signature_signtool'
and 'virtio_vga_visual_test.check_driver_signature_sigverif'
two cases

Signed-off-by: Dehan Meng <[email protected]>
Copy link

@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

🤖 Fix all issues with AI agents
In `@qemu/tests/virtio_vga_visual_test.py`:
- Around line 737-740: The call to self.session.cmd(cmd) using the
start_menu_cmd may raise a timeout/command error but is not handled, causing a
misleading "Start Menu did not open" failure; wrap the invocation of
start_menu_cmd (the variable start_menu_cmd) and the call to
self.session.cmd(...) in a try/except (or check the command return/status if the
session API provides one), log or raise a clear test failure on command
execution error, and only proceed to the time.sleep(3) and subsequent menu
checks if the command succeeded (use the same self.session object and the
surrounding block that currently calls session.cmd and time.sleep to implement
this guard).
♻️ Duplicate comments (2)
qemu/tests/virtio_vga_visual_test.py (2)

1309-1312: Inconsistent reboot call - missing session parameter.

The vm.reboot() call at line 1311 doesn't pass the session parameter, unlike the similar call in run_reboot_with_driver (lines 1284-1286). This could cause issues with session tracking during system resets.

🔎 Proposed fix
         for i in range(5):
             error_context.context("Reset loop %d/5" % (i + 1), LOG_JOB.info)
-            self.session = self.vm.reboot(method="system_reset", timeout=reboot_timeout)
+            self.session = self.vm.reboot(
+                session=self.session, method="system_reset", timeout=reboot_timeout
+            )
             self._open_session_list.append(self.session)

203-209: RHEL 10+ display server not started after installation.

For RHEL 10+, install_xwayland() is called but no equivalent of start_xvfb() is invoked. The xwayland display won't be running when VNC screenshots are attempted, causing failures on RHEL 10+ hosts.

🔎 Proposed fix
         # Install display server based on RHEL version
         if version >= 10:
             # RHEL 10+: Use xwayland-run
             self.install_xwayland()
+            # Note: xwayland-run is invoked per-command in _vnc_screendump()
+            # No persistent display server needed for this approach
         else:
             # RHEL 8/9: Use Xvfb
             self.install_xvfb()
             self.start_xvfb()

If xwayland-run is indeed meant to wrap each command (as done in _vnc_screendump), add a comment clarifying this design. Otherwise, implement a start_xwayland() method similar to start_xvfb().

Comment on lines +737 to +740
cmd = self.params.get("start_menu_cmd")
self.session.cmd(cmd)

time.sleep(3)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing error handling for Start Menu invocation command.

If the start_menu_cmd fails (e.g., session timeout, command error), the test will proceed to check for menu visibility and fail with a misleading "Start Menu did not open" error instead of indicating the command execution failed.

🔎 Proposed fix
         prompt_start_menu = self.params.get("prompt_start_menu_check")
         cmd = self.params.get("start_menu_cmd")
-        self.session.cmd(cmd)
+        try:
+            self.session.cmd(cmd, timeout=30)
+        except Exception as e:
+            LOG_JOB.warning("Failed to execute start menu command: %s", e)
 
         time.sleep(3)
📝 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
cmd = self.params.get("start_menu_cmd")
self.session.cmd(cmd)
time.sleep(3)
cmd = self.params.get("start_menu_cmd")
try:
self.session.cmd(cmd, timeout=30)
except Exception as e:
LOG_JOB.warning("Failed to execute start menu command: %s", e)
time.sleep(3)
🤖 Prompt for AI Agents
In `@qemu/tests/virtio_vga_visual_test.py` around lines 737 - 740, The call to
self.session.cmd(cmd) using the start_menu_cmd may raise a timeout/command error
but is not handled, causing a misleading "Start Menu did not open" failure; wrap
the invocation of start_menu_cmd (the variable start_menu_cmd) and the call to
self.session.cmd(...) in a try/except (or check the command return/status if the
session API provides one), log or raise a clear test failure on command
execution error, and only proceed to the time.sleep(3) and subsequent menu
checks if the command succeeded (use the same self.session object and the
surrounding block that currently calls session.cmd and time.sleep to implement
this guard).

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.

1 participant