Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions src/winml/modelkit/commands/sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import json
import logging
import os
import platform
import sys
from typing import Any
Expand Down Expand Up @@ -53,26 +54,31 @@ def _get_python_info() -> dict[str, Any]:

def _get_platform_info() -> dict[str, Any]:
"""Gather OS and platform information."""
system = platform.system()
release = platform.release()

# For Windows, use OS class for accurate Windows 11 detection
# platform.release() may incorrectly report '10' on some Python versions
if system == "Windows":
try:
os_info = OS.get()
# Only override if it's actually Windows 11
# Otherwise keep the original platform.release() value
if os_info.is_windows_11():
release = "11"
except Exception:
# Fallback to platform.release() if OS detection fails
pass
# platform.release() may incorrectly report '10' for Windows 11 on some
# Python versions; OS class queries the build number for accurate detection.
try:
if OS.get().is_windows_11():
release = "11"
except Exception:
# Fallback to platform.release() if OS detection fails
pass

# platform.machine() reports the running Python process architecture.
# On ARM64 Windows running an x64 Python under WOW64 emulation,
# PROCESSOR_ARCHITEW6432 holds the true host arch; otherwise
# PROCESSOR_ARCHITECTURE matches the host.
machine = (
os.environ.get("PROCESSOR_ARCHITEW6432")
or os.environ.get("PROCESSOR_ARCHITECTURE")
or platform.machine()
)

return {
"system": system,
"system": platform.system(),
"release": release,
"machine": platform.machine(),
"machine": machine,
"processor": platform.processor() or "Unknown",
}

Expand Down
120 changes: 89 additions & 31 deletions tests/unit/sysinfo/test_sysinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from __future__ import annotations

import os
from unittest.mock import MagicMock, patch


Expand Down Expand Up @@ -128,51 +129,108 @@ def test_windows_detection_fallback_on_exception(
assert result["release"] == "10"
assert result["machine"] == "AMD64"

@patch("winml.modelkit.commands.sys.OS")
@patch("winml.modelkit.commands.sys.platform")
def test_non_windows_platform(self, mock_platform: MagicMock) -> None:
"""Test non-Windows platforms pass through unchanged."""
def test_processor_unknown_fallback(
self, mock_platform: MagicMock, mock_os_class: MagicMock
) -> None:
"""Test processor defaults to 'Unknown' when empty."""
from winml.modelkit.commands.sys import _get_platform_info

# Setup mocks for Linux
mock_platform.system.return_value = "Linux"
mock_platform.release.return_value = "5.15.0"
mock_platform.machine.return_value = "x86_64"
mock_platform.processor.return_value = "x86_64"
mock_platform.system.return_value = "Windows"
mock_platform.release.return_value = "10"
mock_platform.machine.return_value = "AMD64"
mock_platform.processor.return_value = "" # Empty string

result = _get_platform_info()
mock_os_instance = MagicMock()
mock_os_instance.is_windows_11.return_value = False
mock_os_class.get.return_value = mock_os_instance

with patch.dict(os.environ, {"PROCESSOR_ARCHITECTURE": "AMD64"}, clear=True):
result = _get_platform_info()

assert result["processor"] == "Unknown"

assert result["system"] == "Linux"
assert result["release"] == "5.15.0"
assert result["machine"] == "x86_64"

class TestWindowsHostArchitecture:
"""Test that Windows machine architecture reports the host, not the Python process arch.

Regression coverage for https://github.com/microsoft/WinML-ModelKit/issues/58:
`platform.machine()` returns the running Python process architecture, which is
incorrect on ARM64 Windows when an x64 Python runs under WOW64 emulation.
"""

@patch("winml.modelkit.commands.sys.OS")
@patch("winml.modelkit.commands.sys.platform")
def test_macos_platform(self, mock_platform: MagicMock) -> None:
"""Test macOS platforms pass through unchanged."""
def test_native_arm64_python_reports_arm64(
self, mock_platform: MagicMock, mock_os_class: MagicMock
) -> None:
"""Native ARM64 Python on ARM64 Windows must report ARM64."""
from winml.modelkit.commands.sys import _get_platform_info

# Setup mocks for macOS
mock_platform.system.return_value = "Darwin"
mock_platform.release.return_value = "21.6.0"
mock_platform.machine.return_value = "arm64"
mock_platform.processor.return_value = "arm"
mock_platform.system.return_value = "Windows"
mock_platform.release.return_value = "10"
mock_platform.machine.return_value = "ARM64"
mock_platform.processor.return_value = "ARMv8 (64-bit)"

result = _get_platform_info()
mock_os_instance = MagicMock()
mock_os_instance.is_windows_11.return_value = True
mock_os_class.get.return_value = mock_os_instance

with patch.dict(os.environ, {"PROCESSOR_ARCHITECTURE": "ARM64"}, clear=True):
result = _get_platform_info()

assert result["system"] == "Darwin"
assert result["release"] == "21.6.0"
assert result["machine"] == "arm64"
assert result["machine"] == "ARM64"

@patch("winml.modelkit.commands.sys.OS")
@patch("winml.modelkit.commands.sys.platform")
def test_processor_unknown_fallback(self, mock_platform: MagicMock) -> None:
"""Test processor defaults to 'Unknown' when empty."""
def test_x64_emulated_python_on_arm64_reports_arm64(
self, mock_platform: MagicMock, mock_os_class: MagicMock
) -> None:
"""x64-emulated Python on ARM64 Windows must report ARM64, not AMD64.

Under WOW64 emulation, PROCESSOR_ARCHITECTURE reflects the emulated
process arch (AMD64) and PROCESSOR_ARCHITEW6432 holds the true host
arch (ARM64). The host arch is what users want to see.
"""
from winml.modelkit.commands.sys import _get_platform_info

# Setup mocks
mock_platform.system.return_value = "Linux"
mock_platform.release.return_value = "5.15.0"
mock_platform.machine.return_value = "x86_64"
mock_platform.processor.return_value = "" # Empty string
mock_platform.system.return_value = "Windows"
mock_platform.release.return_value = "11"
mock_platform.machine.return_value = "AMD64"
mock_platform.processor.return_value = "Intel64 Family 6"

result = _get_platform_info()
mock_os_instance = MagicMock()
mock_os_instance.is_windows_11.return_value = True
mock_os_class.get.return_value = mock_os_instance

assert result["processor"] == "Unknown"
with patch.dict(
os.environ,
{"PROCESSOR_ARCHITECTURE": "AMD64", "PROCESSOR_ARCHITEW6432": "ARM64"},
clear=True,
):
result = _get_platform_info()

assert result["machine"] == "ARM64"

@patch("winml.modelkit.commands.sys.OS")
@patch("winml.modelkit.commands.sys.platform")
def test_native_amd64_python_reports_amd64(
self, mock_platform: MagicMock, mock_os_class: MagicMock
) -> None:
"""Native AMD64 Python on AMD64 Windows must still report AMD64."""
from winml.modelkit.commands.sys import _get_platform_info

mock_platform.system.return_value = "Windows"
mock_platform.release.return_value = "11"
mock_platform.machine.return_value = "AMD64"
mock_platform.processor.return_value = "Intel64 Family 6"

mock_os_instance = MagicMock()
mock_os_instance.is_windows_11.return_value = True
mock_os_class.get.return_value = mock_os_instance

with patch.dict(os.environ, {"PROCESSOR_ARCHITECTURE": "AMD64"}, clear=True):
result = _get_platform_info()

assert result["machine"] == "AMD64"
Loading