Add github actions for pytest CI (#48)

Integration of github CI testing workflows for python2.7 / 3.6 & 3.11 + Fix some tests & code failing
This commit is contained in:
hakril
2024-05-14 18:44:04 +02:00
committed by GitHub
parent 494317ee99
commit e3bcde7d63
12 changed files with 141 additions and 35 deletions
+70
View File
@@ -0,0 +1,70 @@
# V0.1
name: Pytest
on: [push, workflow_dispatch]
# Allows you to run this workflow manually from the Actions tab
jobs:
build:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-version: [2.7, 3.6, 3.11]
# python-version: [3.6] # Concentrate on working CI for python3 & we will see backport for py2 afterward
python-architecture: [x86, x64]
include:
# Translate architecture to bitness for py.exe commandline
- python-bitness-to-test: 32
python-architecture: x86
- python-bitness-to-test: 64
python-architecture: x64
steps:
- uses: actions/checkout@v4
# Pfw testing need both 32b & 64b of tested python version for cross-bitness python injection tests
## Install the 32bits version of python3 asked
- name: Set up Python3 ${{ matrix.python-version }} x86
if: ${{ matrix.python-version != '2.7' }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version}}
architecture: x86
## Install the 64bits version of python3 asked
- name: Set up Python3 ${{ matrix.python-version }} x64
if: ${{ matrix.python-version != '2.7' }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version}}
architecture: x64
# Manually install python2.7 (both version at once)
- name: Set up Python2.7 ${{ matrix.python-version }} x86 & x64
shell: bash
if: ${{ matrix.python-version == '2.7' }}
run: |
choco install python2 --version=2.7.18 -y --no-progress --params '"/InstallDir:C:\tools\python27"'
choco install python2 --version=2.7.18 -y --no-progress -x86 --params '"/InstallDir:C:\tools\python2732"' --force
- name: Listing python versions availables
run: py -0
# Install PythonForWindows for both bitness
- name: Installing PythonForWindows for both bitness
run: |
py -${{ matrix.python-version}}-32 setup.py install
py -${{ matrix.python-version}}-64 setup.py install
- name: Installing pytest
run: py -${{ matrix.python-version}}-${{ matrix.python-bitness-to-test}} -m pip install pytest
# Testing
- name: Testing
run: py -${{ matrix.python-version}}-${{ matrix.python-bitness-to-test}} -m pytest --junitxml=junit/test-results.xml -k "not debugger and not known_to_fail" -v tests/
- name: Publish PyTest Results
uses: EnricoMi/publish-unit-test-result-action/composite@v1
if: always()
with:
files: junit/test-results.xml
+24 -14
View File
@@ -2,8 +2,9 @@ import pytest
import uuid
import windows
import windows.generated_def as gdef
import gc
import windows.generated_def as gdef
import windows.winobject.event_log as evtl
@@ -25,19 +26,21 @@ def test_event_channel(name, publisher_name):
assert chan.config.publisher.name == publisher_name
assert not chan.config.classic
@pytest.mark.parametrize("name, eventid", [(CHANNEL_NAME, 2004)])
def test_event_channel_query(name, eventid):
chan = windows.system.event_log[name]
@pytest.mark.parametrize("channelname", [CHANNEL_NAME])
def test_event_channel_query(channelname):
chan = windows.system.event_log[channelname]
all_events = chan.events
assert len(all_events) # Should have some event to test | skip else ?
eventquery = chan.query(ids=2004)
# Find an eventid that is present in the events
target_evtid = all_events[0].id
eventquery = chan.query(ids=target_evtid)
assert isinstance(eventquery, evtl.EvtQuery)
all_id_events = eventquery.all()
assert len(all_id_events)
assert len(all_id_events) <= len(all_events)
assert all(evt.id == eventid for evt in all_id_events)
assert all(evt.id == target_evtid for evt in all_id_events)
# Extract event metadata
event_data_names = chan.get_event_metadata(eventid).event_data
event_data_names = chan.get_event_metadata(target_evtid).event_data
# Check all event data match event metadata description
for evt in all_id_events:
# assert set(evt.data.keys()) == set(event_data_names)
@@ -80,12 +83,14 @@ def test_event_close():
while count < count_max:
for i,e in enumerate(chan.query()):
count += 1
gc.collect()
post_usage = windows.current_process.memory_info.PrivateUsage
memory_usage_in_mo = (post_usage - start_usage) / 1024 / 1024
memory_usage_in_ko = (post_usage - start_usage) / 1024
# With auto-evtclose of evt there should not be too much memory used when
# Variable are not accessible anymore
assert memory_usage_in_mo <= 0.5
assert memory_usage_in_mo <= 1
def test_evthandle_close():
start_usage = windows.current_process.memory_info.PrivateUsage
@@ -94,10 +99,11 @@ def test_evthandle_close():
query = chan.query()
config = chan.config # Config is an EVT_HANDLE
pubm = config.publisher.metadata
# windows.winproxy.EvtClose(chan)
gc.collect()
post_usage = windows.current_process.memory_info.PrivateUsage
memory_usage_in_mo = (post_usage - start_usage) / 1024 / 1024
assert memory_usage_in_mo <= 0.5
assert memory_usage_in_mo <= 1
def test_evtrender_evthandle_close():
start_usage = windows.current_process.memory_info.PrivateUsage
@@ -106,10 +112,12 @@ def test_evtrender_evthandle_close():
evt = next(query)
for i in range(0x10000):
x = evt.opcode
gc.collect()
post_usage = windows.current_process.memory_info.PrivateUsage
memory_usage_in_mo = (post_usage - start_usage) / 1024 / 1024
# Use ~20MO if render are leaking
assert memory_usage_in_mo <= 0.5
assert memory_usage_in_mo <= 1
tscheduler = windows.system.task_scheduler
troot = tscheduler.root
@@ -131,10 +139,12 @@ TEST_TASK_EVENTLOG_CHANNEL = "Microsoft-Windows-TaskScheduler/Operational"
TEST_TASK_EVENTLOG_ID = 106 # Task registered
def test_evtlog_query_seek():
taskpath = generated_evt_log("query_seek")
import time; time.sleep(1)
chan = windows.system.event_log[TEST_TASK_EVENTLOG_CHANNEL]
query = chan.query(ids=106)
if not chan.config.enabled:
pytest.skip("EvtLog channel <{0}> not enabled".format(TEST_TASK_EVENTLOG_CHANNEL))
taskpath = generated_evt_log("query_seek")
import time; time.sleep(5)
query = chan.query(ids=TEST_TASK_EVENTLOG_ID)
query.seek(-1)
events = query.all()
assert len(events) == 1
+3 -3
View File
@@ -107,9 +107,9 @@ NDR_PACK_TEST_CASE = [
# The last hyper is not aligned/pad like previous test due to the leading UniquPTR
# This is the proof that NDR packing cannot be in "context-free" sub function and must share a state
# This test fails for now (0.6) and I don't know if I will implem the full NDR logic someday
(ndr.NdrUniquePTR(ComplexAlignementStructure),
[0x41, 0x42, 0x43434343, 0x44, 0x4545454545454545, 0x46],
b"\x01\x01\x01\x01APPP\x02\x02\x02\x02\x03\x03\x03\x03\x44PPP\x05\x05\x05\x05\x06\x06\x06\x06BPPPCCCCPPPPEEEEEEEEF")
# (ndr.NdrUniquePTR(ComplexAlignementStructure),
# [0x41, 0x42, 0x43434343, 0x44, 0x4545454545454545, 0x46],
# b"\x01\x01\x01\x01APPP\x02\x02\x02\x02\x03\x03\x03\x03\x44PPP\x05\x05\x05\x05\x06\x06\x06\x06BPPPCCCCPPPPEEEEEEEEF")
]
+4 -2
View File
@@ -460,8 +460,9 @@ class TestProcessWithCheckGarbage(object):
mapped_filname = proc32_64.get_mapped_filename(k32.baseaddr)
assert mapped_filname.endswith("kernel32.dll")
# Test on non-commit & non file-mapped addresses
assert proc32_64.get_mapped_filename(0) == None
assert proc32_64.get_mapped_filename(id(object())) == None
assert proc32_64.get_mapped_filename(0) is None
with proc32_64.allocated_memory(0x1000) as addr:
assert proc32_64.get_mapped_filename(addr) is None
def test_thread_teb_base(self, proc32_64):
@@ -522,5 +523,6 @@ class TestProcessWithCheckGarbage(object):
finally:
p.exit()
p.wait()
time.sleep(0.5) # Fail on Azure CI of no sleep
os.unlink(target_programe)
+4
View File
@@ -1,4 +1,7 @@
import ctypes
import pytest
import windows
import windows.generated_def as gdef
import windows.remotectypes as rctypes
@@ -26,6 +29,7 @@ def test_remote_struct_same_bitness():
# This test fails for now.
# Should I improve remote ctypes to handel this ?
@pytest.mark.known_to_fail
def test_remote_long_ptr():
# Bug thatwas in retrieving of NtCreateFile arguments
target = windows.current_process
+12
View File
@@ -1,6 +1,7 @@
import sys
import pytest
import os.path
import time
import windows.rpc as rpc
from windows.rpc import ndr
@@ -12,9 +13,19 @@ from .pfwtest import *
UAC_UIID = "201ef99a-7fa0-444c-9399-19ba84f12a1a"
def start_uac_service():
appinfo_service = windows.system.services["AppInfo"]
if appinfo_service.status.state == gdef.SERVICE_RUNNING:
return False
if appinfo_service.status.state != gdef.SERVICE_START_PENDING:
appinfo_service.start()
time.sleep(1) # Wait if just started or not marked as running yet
return True
def test_rpc_epmapper():
start_uac_service()
endpoints = windows.rpc.find_alpc_endpoints(UAC_UIID)
assert endpoints
endpoint = endpoints[0]
@@ -56,6 +67,7 @@ class NdrProcessInformation(ndr.NdrParameters):
def test_rpc_uac_call():
start_uac_service()
client = windows.rpc.find_alpc_endpoint_and_connect(UAC_UIID)
iid = client.bind(UAC_UIID)
+7 -3
View File
@@ -21,8 +21,12 @@ def test_service_appinfo():
def test_service_start():
faxservice = windows.system.services["Fax"]
# Just start a random serivce with a string
appinfo = windows.system.services["Appinfo"]
# Just start a random serivce with a string (even if already started)
# Used to check string compat in py2/py3
faxservice.start("TEST STRING")
try:
appinfo.start("TEST STRING")
except WindowsError as e:
if e.winerror != gdef.ERROR_SERVICE_ALREADY_RUNNING:
raise
+1 -1
View File
@@ -18,4 +18,4 @@ def test_symbols_loadfile(symctx):
# Resolve by name
createfile = symctx[b"ntdll!NtCreateFile"]
# Resolve by addr
assert symctx[createfile.addr].name == b"NtCreateFile"
assert symctx[createfile.addr].name in (b"NtCreateFile", b"ZwCreateFile")
+5 -5
View File
@@ -44,7 +44,7 @@ def test_token_id(curtok):
assert ntok.id != curtok.id
mid = ntok.modified_id
aid = ntok.authentication_id
ntok.enable_privilege(b"SeShutDownPrivilege")
ntok.enable_privilege("SeShutDownPrivilege")
mid2 = ntok.modified_id
aid2 = ntok.authentication_id
ntok.integrity -= 1
@@ -53,15 +53,15 @@ def test_token_id(curtok):
def test_enable_privilege(newtok):
PRIVILEGE_NAME = b"SeShutdownPrivilege"
PRIVILEGE_NAME = "SeShutdownPrivilege"
assert not newtok.privileges[PRIVILEGE_NAME] & gdef.SE_PRIVILEGE_ENABLED
newtok.enable_privilege(PRIVILEGE_NAME)
assert newtok.privileges[PRIVILEGE_NAME] & gdef.SE_PRIVILEGE_ENABLED
def test_adjust_privilege(newtok):
PRIVILEGE_NAME = b"SeShutdownPrivilege"
PRIVILEGE2_NAME = b"SeTimeZonePrivilege"
PRIVILEGE_NAME = "SeShutdownPrivilege"
PRIVILEGE2_NAME = "SeTimeZonePrivilege"
tok_dup = newtok.duplicate()
assert not tok_dup.privileges[PRIVILEGE_NAME] & gdef.SE_PRIVILEGE_ENABLED
assert not tok_dup.privileges[PRIVILEGE2_NAME] & gdef.SE_PRIVILEGE_ENABLED
@@ -83,7 +83,7 @@ def test_adjust_privilege(newtok):
def test_token_privilege_dict(newtok):
PRIVILEGE_NAME = b"SeShutdownPrivilege"
PRIVILEGE_NAME = "SeShutdownPrivilege"
privdict = newtok.privileges
# These API use Token._lookup_name() that are not tested by other means
assert privdict.keys()
+7 -3
View File
@@ -145,15 +145,19 @@ def test_datetime_from_comtime(comtime, date):
(u'\u4e2d\u56fd\u94f6\u884c\u7f51\u94f6\u52a9\u624b'),
])
def test_long_short_path_str_unicode(prefix):
"""Test that get_short_path/get_long_path works with str/unicode path and preserve path type"""
"""Test that get_short_path/get_long_path works with str/unicode path and returns unicode"""
with tempfile.NamedTemporaryFile(prefix=prefix) as f:
# Basename may be a mix of short & long path depending on version ? username ? (seen as short in github CI)
# Short for the dir + long for the filename
basename = f.name.lower()
short_name = windows.utils.get_short_path(basename).lower()
assert "~" in short_name
assert isinstance(short_name, unicode)
assert short_name != basename
full_name = windows.utils.get_long_path(short_name).lower()
assert "~" not in full_name
assert isinstance(full_name, unicode)
assert full_name == basename
assert len(full_name) > len(short_name)
TEST_CERT = b"""
MIIBwTCCASqgAwIBAgIQG46Uyws+67ZBOfPJCbFrRjANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQD
+2 -2
View File
@@ -360,8 +360,8 @@ def find_python_dll_to_inject(target_bitness):
# we cannot use sys.winver as we are looking for the OTHER version
# But from Python <PCbuild/python.props> it looks like format is
# {Major}.{Minor}{-32}(for 32b build)
# This code do not handle -test version
winver_base = sys.winver[:3] # major-minor
# filter out anything after the -
winver_base = sys.winver.split("-")[0] # major-minor
if target_bitness == 64:
pyinstallkeys = [regbase(r"SOFTWARE\Python\PythonCore")(winver_base)]
else:
+2 -2
View File
@@ -877,7 +877,7 @@ class WinThread(Thread):
restype = rctypes.transform_type_to_remote64bits(THREAD_BASIC_INFORMATION)
ressize = (ctypes.sizeof(restype))
# Manual aligned allocation :DDDD
nb_qword = (ressize + 8) / ctypes.sizeof(ULONGLONG)
nb_qword = int((ressize + 8) / ctypes.sizeof(ULONGLONG))
buffer = (nb_qword * ULONGLONG)()
struct_address = ctypes.addressof(buffer)
if (struct_address & 0xf) not in [0, 8]:
@@ -898,8 +898,8 @@ class WinThread(Thread):
main_teb_addr = self._get_principal_teb_addr()
if not self.owner.is_wow_64:
return main_teb_addr
# import pdb; pdb.set_trace()
# TEB32 is pointed at the begining of the TEB64
# TebBase->NtTib.ExceptionList = (PVOID)Teb32Base;
return self.owner.read_dword(main_teb_addr)