Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 4 additions & 4 deletions .github/workflows/install-vs-components.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
text=True,
shell=True,
).strip()
components_to_add = (
["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64"]
components_to_add = [
"Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64"
if platform.machine() == "ARM64"
else ["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL"]
)
else "Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL"
]
args = (
"vs_installer.exe",
"modify",
Expand Down
33 changes: 22 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,25 @@ concurrency:
jobs:
test:
name: Build and test
runs-on: windows-2022
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
architecture: [x64, x86]
python-architecture: [x64, x86, arm64]
include:
- os: windows-2022
- python-architecture: arm64
os: windows-11-arm
Comment on lines +25 to +28
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is saying "add os as a matrix element that defaults to windows-2022" and "when python-architecture == arm64, use windows-11-arm instead"

exclude:
# actions/setup-python does not provide prebuilt arm64 Python before 3.11
- python-architecture: arm64
python-version: "3.8"
- python-architecture: arm64
python-version: "3.9"
- python-architecture: arm64
python-version: "3.10"
env:
# TODO: We can't yet run tests with PYTHONDEVMODE=1, let's emulated it as much as we can
# https://docs.python.org/3/library/devmode.html#effects-of-the-python-development-mode
Expand All @@ -37,7 +49,7 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
architecture: ${{ matrix.python-architecture }}
cache: pip
cache-dependency-path: .github/workflows/main.yml
check-latest: true
Expand Down Expand Up @@ -83,21 +95,20 @@ jobs:
# Upload artifacts even if tests fail
if: ${{ always() }}
with:
name: artifacts-${{ matrix.python-version }}-${{ matrix.architecture }}
name: artifacts-${{ matrix.python-version }}-${{ matrix.python-architecture }}
path: dist/*.whl
if-no-files-found: error

# We cannot build and test on ARM64, so we cross-compile.
# Later, when available, we can add tests using this wheel on ARM64 VMs
build_arm64:
name: Cross-compile ARM
# actions/setup-python does not provide prebuilt arm64 Python before 3.11, so we cross-compile.
cross_compile_arm64:
name: Cross-compile ARM64
runs-on: windows-2022
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
# pythonarm64 NuGet's has no download for Python ~=3.9.11
python-version: ["3.9.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
# pythonarm64 NuGet has no download for Python 3.8 and Python ~=3.9.11
python-version: ["3.9.10", "3.10"]
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -128,7 +139,7 @@ jobs:

merge:
runs-on: windows-latest
needs: [test, build_arm64]
needs: [test, cross_compile_arm64]
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
Expand Down
12 changes: 11 additions & 1 deletion com/win32com/test/testArrays.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# Originally contributed by Stefan Schukat as part of this arbitrary-sized
# arrays patch.
from __future__ import annotations

import platform
import unittest

from win32com.client import gencache
from win32com.test import util

ZeroD = 0
OneDEmpty = []
OneDEmpty: list[int] = []
OneD = [1, 2, 3]
TwoD = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]

Expand Down Expand Up @@ -49,6 +53,12 @@ def _normalize_array(a):
return ret


@unittest.skipIf(
platform.machine() == "ARM64",
"PyCOMTest.ArrayTest cannot currently be run on ARM64 "
+ "due to lacking win32com.universal implementation "
+ "in com/win32com/src/univgw.cpp",
)
class ArrayTest(util.TestCase):
def setUp(self):
self.arr = gencache.EnsureDispatch("PyCOMTest.ArrayTest", bForDemand=False)
Expand Down
41 changes: 21 additions & 20 deletions com/win32com/test/testPyComTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import win32timezone
import winerror
from win32api import CloseHandle, GetCurrentProcessId, OpenProcess
from win32com import universal
from win32com.client import (
VARIANT,
CastTo,
Expand All @@ -25,6 +24,7 @@
gencache,
register_record_class,
)
from win32com.universal import RegisterInterfaces
from win32process import GetProcessMemoryInfo

# This test uses a Python implemented COM server - ensure correctly registered.
Expand All @@ -46,10 +46,6 @@
print(f"The PyCOMTest module can not be located or generated.\n{importMsg}\n")
raise RuntimeError(importMsg) from error

# We had a bg where RegisterInterfaces would fail if gencache had
# already been run - exercise that here
universal.RegisterInterfaces("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1)

verbose = 0


Expand Down Expand Up @@ -175,7 +171,7 @@ def _DumpFireds(self):
if not self.fireds:
print("ERROR: Nothing was received!")
for firedId, no in self.fireds.items():
progress("ID %d fired %d times" % (firedId, no))
progress(f"ID {firedId} fired {no} times")


# Test everything which can be tested using both the "dynamic" and "generated"
Expand Down Expand Up @@ -891,34 +887,39 @@ def TestVTableMI():
pass


def TestQueryInterface(long_lived_server=0, iterations=5):
def TestQueryInterface(long_lived_server=False, iterations=5):
tester = win32com.client.Dispatch("PyCOMTest.PyCOMTest")
if long_lived_server:
# Create a local server
t0 = win32com.client.Dispatch(
"Python.Test.PyCOMTest", clsctx=pythoncom.CLSCTX_LOCAL_SERVER
)
# Request custom interfaces a number of times
prompt = [
"Testing QueryInterface without long-lived local-server #%d of %d...",
"Testing QueryInterface with long-lived local-server #%d of %d...",
]
# Request custom interfaces a number of time

for i in range(iterations):
progress(prompt[long_lived_server != 0] % (i + 1, iterations))
progress(
f"Testing QueryInterface "
+ ("with" if long_lived_server else "without")
+ f" long-lived local-server #{i + 1} of {iterations}..."
)
tester.TestQueryInterface()


class Tester(win32com.test.util.TestCase):
def testVTableInProc(self):
def testRegisterInterfacesAfterGencache(self) -> None:
# We had a bug where RegisterInterfaces would fail if gencache had
# already been run - exercise that here
RegisterInterfaces("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1)

def testVTableInProc(self) -> None:
# We used to crash running this the second time - do it a few times
for i in range(3):
progress("Testing VTables in-process #%d..." % (i + 1))
progress(f"Testing VTables in-process #{(i + 1)}...")
TestVTable(pythoncom.CLSCTX_INPROC_SERVER)

def testVTableLocalServer(self):
def testVTableLocalServer(self) -> None:
for i in range(3):
progress("Testing VTables out-of-process #%d..." % (i + 1))
progress(f"Testing VTables out-of-process #{(i + 1)}...")
TestVTable(pythoncom.CLSCTX_LOCAL_SERVER)

def testVTable2(self):
Expand All @@ -930,15 +931,15 @@ def testVTableMI(self):
TestVTableMI()

def testMultiQueryInterface(self):
TestQueryInterface(0, 6)
TestQueryInterface(False, 6)
# When we use the custom interface in the presence of a long-lived
# local server, i.e. a local server that is already running when
# we request an instance of our COM object, and remains afterwards,
# then after repeated requests to create an instance of our object
# the custom interface disappears -- i.e. QueryInterface fails with
# E_NOINTERFACE. Set the upper range of the following test to 2 to
# pass this test, i.e. TestQueryInterface(1,2)
TestQueryInterface(1, 6)
# pass this test, i.e. TestQueryInterface(True, 2)
TestQueryInterface(True, 6)

def testDynamic(self):
TestDynamic()
Expand Down
17 changes: 11 additions & 6 deletions com/win32com/test/testall.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getopt
import os
import platform
import re
import sys
import traceback
Expand All @@ -22,7 +23,6 @@
win32com.__path__[0] = win32com_src_dir

import pythoncom
import win32com.client
from pywin32_testutil import TestLoader, TestRunner
from win32com.test.util import (
CapturingFunctionTestCase,
Expand Down Expand Up @@ -52,7 +52,7 @@ def CleanGenerated():

if os.path.isdir(win32com.__gen_path__):
if verbosity > 1:
print("Deleting files from %s" % (win32com.__gen_path__))
print(f"Deleting files from", win32com.__gen_path__)
shutil.rmtree(win32com.__gen_path__)
import win32com.client.gencache

Expand All @@ -78,11 +78,17 @@ def ExecuteSilentlyIfOK(cmd, testcase):
rc = f.close()
if rc:
print(data)
testcase.fail("Executing '%s' failed (%d)" % (cmd, rc))
testcase.fail(f"Executing '{cmd}' failed ({rc})")
# for "_d" builds, strip the '[xxx refs]' line
return RemoveRefCountOutput(data)


@unittest.skipIf(
platform.machine() == "ARM64",
"PyCOMTest cannot currently be run on ARM64 "
+ "due to lacking win32com.universal implementation "
+ "in com/win32com/src/univgw.cpp",
)
class PyCOMTest(TestCase):
no_leak_tests = True # done by the test itself

Expand Down Expand Up @@ -291,8 +297,7 @@ def usage(why):
print("These tests may take *many* minutes to run - be patient!")
print("(running from python.exe will avoid these leak tests)")
print(
"Executing level %d tests - %d test cases will be run"
% (testLevel, suite.countTestCases())
f"Executing level {testLevel} tests - {suite.countTestCases()} test cases will be run"
)
if verbosity == 1 and suite.countTestCases() < 70:
# A little row of markers so the dots show how close to finished
Expand All @@ -307,7 +312,7 @@ def usage(why):
desc = "\n".join(traceback.format_exception_only(exc_type, exc_val))
testResult.stream.write(f"{mod_name}: {desc}")
testResult.stream.writeln(
"*** %d test(s) could not be run ***" % len(import_failures)
f"*** {len(import_failures)} test(s) could not be run ***"
)

# re-print unit-test error here so it is noticed
Expand Down
2 changes: 1 addition & 1 deletion make_all.bat
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ py -3.13 -m build --wheel
py -3.14-32 -m build --wheel
py -3.14 -m build --wheel

rem Check /build_env.md#build-environment to make sure you have all the required ARM64 components installed
rem Check /build_env.md#cross-compiling-for-arm64-microsoft-visual-c-141-and-up to make sure you have all the required ARM64 components installed
py -3.10 -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"
py -3.11 -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"
py -3.12 -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"
Expand Down
Loading