Skip to content

Commit c2c2fe5

Browse files
authored
Extract Time Utility Helpers and Add Moniker/ROT Time-Related Tests (#914)
* refactor: Extract time structure helpers to `time_structs_helper.py` Moved `SYSTEMTIME` structure and related `kernel32` API calls, `SystemTimeToFileTime` and `CompareFileTime` helper functions from `test/test_storage.py` to `test/time_structs_helper.py`. This improves modularity and reusability of time-related utility functions within the test suite. * test: Add tests for `IRunningObjectTable` change time methods. This commit introduces a new test class to `test/test_rot.py`. It verifies the functionality of `NoteChangeTime` and `GetTimeOfLastChange` by setting and retrieving the last change time of a registered COM object. * test: Add test for `IMoniker.GetTimeOfLastChange` method. It verifies the functionality of `IMoniker.GetTimeOfLastChange` for file monikers by creating and modifying a temporary file and asserting that the `FILETIME` returned by `GetTimeOfLastChange` reflects the modification.
1 parent e51ff94 commit c2c2fe5

4 files changed

Lines changed: 92 additions & 48 deletions

File tree

comtypes/test/test_moniker.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import ctypes
33
import os
44
import tempfile
5+
import time
56
import unittest
67
from _ctypes import COMError
78
from ctypes import POINTER, WinDLL, byref
@@ -27,6 +28,7 @@
2728
_CreateItemMoniker,
2829
_GetRunningObjectTable,
2930
)
31+
from comtypes.test.time_structs_helper import CompareFileTime
3032

3133
with contextlib.redirect_stdout(None): # supress warnings
3234
GetModule("msvidctl.dll")
@@ -176,6 +178,24 @@ def test_item(self):
176178
self.assertEqual(mon.IsRunning(bctx, None, None), hresult.S_FALSE)
177179

178180

181+
class Test_GetTimeOfLastChange(unittest.TestCase):
182+
def test_file(self):
183+
bctx = _create_bctx()
184+
with tempfile.NamedTemporaryFile() as f:
185+
tmpfile = Path(f.name)
186+
f.write(b"test data")
187+
# Create a File Moniker for the temporary file
188+
file_mon = _create_file_moniker(str(tmpfile))
189+
# Get initial time of last change for the file
190+
initial_ft = file_mon.GetTimeOfLastChange(bctx, None)
191+
# Modify the file to change its last write time
192+
time.sleep(0.01) # Ensure a different timestamp
193+
os.write(f.fileno(), b"more data")
194+
after_change_ft = file_mon.GetTimeOfLastChange(bctx, None)
195+
# Verify the time has changed (after_change_ft > initial_ft)
196+
self.assertEqual(CompareFileTime(after_change_ft, initial_ft), 1)
197+
198+
179199
class Test_CommonPrefixWith(unittest.TestCase):
180200
def test_file(self):
181201
bctx = _create_bctx()

comtypes/test/test_rot.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
_CreateItemMoniker,
1313
_GetRunningObjectTable,
1414
)
15+
from comtypes.test.time_structs_helper import (
16+
SYSTEMTIME,
17+
CompareFileTime,
18+
SystemTimeToFileTime,
19+
)
1520

1621
with contextlib.redirect_stdout(None): # supress warnings
1722
GetModule("msvidctl.dll")
@@ -74,3 +79,16 @@ def test_returns_enum_moniker(self):
7479
rot = _create_rot()
7580
enum_moniker = rot.EnumRunning()
7681
self.assertIsInstance(enum_moniker, IEnumMoniker)
82+
83+
84+
class Test_NoteChangeTime_GetTimeOfLastChange(unittest.TestCase):
85+
def test_modified_time(self):
86+
vidctl = CreateObject(msvidctl.MSVidCtl, interface=msvidctl.IMSVidCtl)
87+
item_id = str(GUID.create_new())
88+
mon = _create_item_moniker("!", item_id)
89+
rot = _create_rot()
90+
dw_reg = rot.Register(ROTFLAGS_ALLOWANYCLIENT, vidctl, mon)
91+
ft = SystemTimeToFileTime(SYSTEMTIME(wYear=2000, wMonth=1, wDay=1))
92+
rot.NoteChangeTime(dw_reg, ft)
93+
self.assertEqual(CompareFileTime(rot.GetTimeOfLastChange(mon), ft), 0)
94+
rot.Revoke(dw_reg)

comtypes/test/test_storage.py

Lines changed: 15 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,23 @@
33
import tempfile
44
import unittest
55
from _ctypes import COMError
6-
from ctypes import HRESULT, POINTER, OleDLL, Structure, WinDLL, byref, c_ubyte
7-
from ctypes.wintypes import BOOL, DWORD, FILETIME, LONG, PWCHAR, WORD
6+
from ctypes import HRESULT, POINTER, OleDLL, byref, c_ubyte
7+
from ctypes.wintypes import DWORD, FILETIME, PWCHAR
88
from pathlib import Path
99
from typing import Optional
1010

1111
import comtypes
1212
import comtypes.client
1313
from comtypes.malloc import CoGetMalloc
14+
from comtypes.test.time_structs_helper import (
15+
SYSTEMTIME,
16+
CompareFileTime,
17+
SystemTimeToFileTime,
18+
)
1419

1520
comtypes.client.GetModule("portabledeviceapi.dll")
1621
from comtypes.gen.PortableDeviceApiLib import WSTRING, IStorage, tagSTATSTG
1722

18-
19-
class SYSTEMTIME(Structure):
20-
_fields_ = [
21-
("wYear", WORD),
22-
("wMonth", WORD),
23-
("wDayOfWeek", WORD),
24-
("wDay", WORD),
25-
("wHour", WORD),
26-
("wMinute", WORD),
27-
("wSecond", WORD),
28-
("wMilliseconds", WORD),
29-
]
30-
31-
32-
_kernel32 = WinDLL("kernel32")
33-
34-
# https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime
35-
_SystemTimeToFileTime = _kernel32.SystemTimeToFileTime
36-
_SystemTimeToFileTime.argtypes = [POINTER(SYSTEMTIME), POINTER(FILETIME)]
37-
_SystemTimeToFileTime.restype = BOOL
38-
39-
# https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-comparefiletime
40-
_CompareFileTime = _kernel32.CompareFileTime
41-
_CompareFileTime.argtypes = [POINTER(FILETIME), POINTER(FILETIME)]
42-
_CompareFileTime.restype = LONG
43-
4423
STGTY_STORAGE = 1
4524

4625
STATFLAG_DEFAULT = 0
@@ -65,16 +44,6 @@ class SYSTEMTIME(Structure):
6544
_StgCreateDocfile.restype = HRESULT
6645

6746

68-
def _systemtime_to_filetime(st: SYSTEMTIME) -> FILETIME:
69-
ft = FILETIME()
70-
_SystemTimeToFileTime(byref(st), byref(ft))
71-
return ft
72-
73-
74-
def _compare_filetime(ft1: FILETIME, ft2: FILETIME) -> int:
75-
return _CompareFileTime(byref(ft1), byref(ft2))
76-
77-
7847
def _get_pwcsname(stat: tagSTATSTG) -> WSTRING:
7948
return WSTRING.from_address(ctypes.addressof(stat) + tagSTATSTG.pwcsName.offset)
8049

@@ -91,9 +60,7 @@ def _create_docfile(self, mode: int, name: Optional[str] = None) -> IStorage:
9160
_StgCreateDocfile(name, mode, 0, byref(stg))
9261
return stg # type: ignore
9362

94-
FIXED_TEST_FILETIME = _systemtime_to_filetime(
95-
SYSTEMTIME(wYear=2000, wMonth=1, wDay=1)
96-
)
63+
FIXED_TEST_FILETIME = SystemTimeToFileTime(SYSTEMTIME(wYear=2000, wMonth=1, wDay=1))
9764

9865
def test_CreateStream(self):
9966
storage = self._create_docfile(mode=self.CREATE_TEMP_TESTDOC)
@@ -218,11 +185,11 @@ def test_SetElementTimes(self):
218185
modified_stat = storage.OpenStorage(
219186
sub_name, None, self.RW_EXCLUSIVE_TX, None, 0
220187
).Stat(STATFLAG_DEFAULT)
221-
self.assertEqual(_compare_filetime(orig_stat.ctime, modified_stat.ctime), 0)
222-
self.assertEqual(_compare_filetime(orig_stat.atime, modified_stat.atime), 0)
223-
self.assertNotEqual(_compare_filetime(orig_stat.mtime, modified_stat.mtime), 0)
188+
self.assertEqual(CompareFileTime(orig_stat.ctime, modified_stat.ctime), 0)
189+
self.assertEqual(CompareFileTime(orig_stat.atime, modified_stat.atime), 0)
190+
self.assertNotEqual(CompareFileTime(orig_stat.mtime, modified_stat.mtime), 0)
224191
self.assertEqual(
225-
_compare_filetime(self.FIXED_TEST_FILETIME, modified_stat.mtime), 0
192+
CompareFileTime(self.FIXED_TEST_FILETIME, modified_stat.mtime), 0
226193
)
227194
with self.assertRaises(COMError) as cm:
228195
storage.SetElementTimes("NonExistent", None, None, self.FIXED_TEST_FILETIME)
@@ -270,9 +237,9 @@ def test_Stat(self):
270237
# Therefore, we only verify that each timestamp is a valid `FILETIME`
271238
# (non-zero is sufficient for a newly created file).
272239
zero_ft = FILETIME()
273-
self.assertNotEqual(_compare_filetime(stat.ctime, zero_ft), 0)
274-
self.assertNotEqual(_compare_filetime(stat.atime, zero_ft), 0)
275-
self.assertNotEqual(_compare_filetime(stat.mtime, zero_ft), 0)
240+
self.assertNotEqual(CompareFileTime(stat.ctime, zero_ft), 0)
241+
self.assertNotEqual(CompareFileTime(stat.atime, zero_ft), 0)
242+
self.assertNotEqual(CompareFileTime(stat.mtime, zero_ft), 0)
276243
# Due to header overhead and file system allocation, the size may be
277244
# greater than 0 bytes.
278245
self.assertGreaterEqual(stat.cbSize, 0)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from ctypes import POINTER, Structure, WinDLL, byref
2+
from ctypes.wintypes import BOOL, FILETIME, LONG, WORD
3+
from typing import Literal
4+
5+
6+
class SYSTEMTIME(Structure):
7+
_fields_ = [
8+
("wYear", WORD),
9+
("wMonth", WORD),
10+
("wDayOfWeek", WORD),
11+
("wDay", WORD),
12+
("wHour", WORD),
13+
("wMinute", WORD),
14+
("wSecond", WORD),
15+
("wMilliseconds", WORD),
16+
]
17+
18+
19+
_kernel32 = WinDLL("kernel32")
20+
21+
# https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime
22+
_SystemTimeToFileTime = _kernel32.SystemTimeToFileTime
23+
_SystemTimeToFileTime.argtypes = [POINTER(SYSTEMTIME), POINTER(FILETIME)]
24+
_SystemTimeToFileTime.restype = BOOL
25+
26+
# https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-comparefiletime
27+
_CompareFileTime = _kernel32.CompareFileTime
28+
_CompareFileTime.argtypes = [POINTER(FILETIME), POINTER(FILETIME)]
29+
_CompareFileTime.restype = LONG
30+
31+
32+
def SystemTimeToFileTime(st: SYSTEMTIME, /) -> FILETIME:
33+
ft = FILETIME()
34+
assert _SystemTimeToFileTime(byref(st), byref(ft))
35+
return ft
36+
37+
38+
def CompareFileTime(ft1: FILETIME, ft2: FILETIME, /) -> Literal[-1, 0, 1]:
39+
return _CompareFileTime(byref(ft1), byref(ft2))

0 commit comments

Comments
 (0)