Skip to content

Commit dbac2ea

Browse files
committed
test_cpuinfo: Improve test coverage
Cover the scenario when entire scope units are offline. Found and fix a bug. Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
1 parent b6c23a5 commit dbac2ea

3 files changed

Lines changed: 84 additions & 68 deletions

File tree

pepclibs/_DieInfo.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -674,8 +674,9 @@ def _discover_noncomp_dies(self, proc_percpuinfo: ProcCpuinfoPerCPUTypedDict):
674674
# Self-check: verify that non-compute die IDs do not overlap with compute die IDs.
675675
for package, pkg_dies in self._noncomp_dies.items():
676676
if package not in self._compute_dies:
677-
# All packages must have compute dies.
678-
raise Error(f"BUG: Package {package} has non-compute dies but no compute dies")
677+
# All CPUs from this package could be offline, so there are no compute dies.
678+
_LOG.debug("Package %d has no compute dies, skipping overlap check", package)
679+
continue
679680
compute_dies_set = set(self._compute_dies[package])
680681
for die in pkg_dies:
681682
if die in compute_dies_set:

tests/common.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515

1616
from pathlib import Path
1717
import typing
18+
from pepclibs import CPUInfo, CPUOnline
1819
from pepclibs.helperlibs import ProcessManager, EmulProcessManager
1920

2021
if typing.TYPE_CHECKING:
21-
from typing import TypedDict, cast
22+
from typing import TypedDict, cast, Generator
2223
from pepclibs.helperlibs.ProcessManager import ProcessManagerType
2324

2425
class CommonTestParamsTypedDict(TypedDict):
@@ -120,3 +121,71 @@ def build_params(pman: ProcessManagerType) -> CommonTestParamsTypedDict:
120121
"""
121122

122123
return {"hostname": pman.hostname, "pman": pman}
124+
125+
# TODO: Most if not all tests should use this, to stress-test the online/offline handling.
126+
def get_cpuinfos(pman: ProcessManagerType) -> Generator[CPUInfo.CPUInfo, None, None]:
127+
"""
128+
Yield 'CPUInfo' objects for testing based on the host type.
129+
130+
Args:
131+
pman: Process manager object that defines the target host.
132+
133+
Yields:
134+
'CPUInfo' objects configured according to the host type for use in tests.
135+
"""
136+
137+
# Ensure that all CPUs are online.
138+
with CPUInfo.CPUInfo(pman=pman) as cpuinfo, \
139+
CPUOnline.CPUOnline(pman=pman, cpuinfo=cpuinfo) as cpuonline:
140+
cpuonline.online()
141+
142+
# Pattern 0: All CPUs online
143+
yield cpuinfo
144+
145+
# Pattern 1: Take odd-numbered CPUs offline.
146+
all_cpus = cpuinfo.get_cpus()
147+
odd_cpus = [cpu for cpu in all_cpus if cpu % 2 == 1]
148+
149+
if odd_cpus:
150+
cpuonline.offline(cpus=odd_cpus)
151+
yield cpuinfo
152+
cpuonline.online(cpus=odd_cpus)
153+
154+
# Pattern 2: Take even-numbered CPUs offline, excluding CPU 0, which can't be offlined.
155+
even_cpus = [cpu for cpu in all_cpus if cpu % 2 == 0 and cpu != 0]
156+
if even_cpus:
157+
cpuonline.offline(cpus=even_cpus)
158+
yield cpuinfo
159+
cpuonline.online(cpus=even_cpus)
160+
161+
# Pattern 3: Take the second core offline.
162+
cores = cpuinfo.get_package_cores(package=0)
163+
if len(cores) > 1:
164+
cpus = cpuinfo.cores_to_cpus(cores=(cores[1],), packages=(0,))
165+
cpuonline.offline(cpus=cpus)
166+
yield cpuinfo
167+
cpuonline.online(cpus=cpus)
168+
169+
# Pattern 4: Take the second module offline.
170+
modules = cpuinfo.get_modules()
171+
if len(modules) > 1:
172+
cpus = cpuinfo.modules_to_cpus(modules=(modules[1],))
173+
cpuonline.offline(cpus=cpus)
174+
yield cpuinfo
175+
cpuonline.online(cpus=cpus)
176+
177+
# Pattern 4: Take the second die offline.
178+
dies = cpuinfo.get_package_dies(package=0)
179+
if len(dies) > 1:
180+
cpus = cpuinfo.dies_to_cpus(dies=(dies[1],), packages=(0,))
181+
cpuonline.offline(cpus=cpus)
182+
yield cpuinfo
183+
cpuonline.online(cpus=cpus)
184+
185+
# Pattern 5: Take the second package offline.
186+
packages = cpuinfo.get_packages()
187+
if len(packages) > 1:
188+
cpus = cpuinfo.packages_to_cpus(packages=(packages[1],))
189+
cpuonline.offline(cpus=cpus)
190+
yield cpuinfo
191+
cpuonline.online(cpus=cpus)

tests/test_cpuinfo.py

Lines changed: 11 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
import random
1818
import pytest
1919
from tests import common
20-
from pepclibs import CPUInfo, CPUOnline
20+
from pepclibs import CPUInfoVars
2121
from pepclibs.helperlibs import Trivial
2222

2323
if typing.TYPE_CHECKING:
2424
from typing import Generator, cast, TypedDict
2525
from tests.common import CommonTestParamsTypedDict
26+
from pepclibs import CPUInfo
2627
from pepclibs.helperlibs.ProcessManager import ProcessManagerType
2728
from pepclibs.CPUInfoTypes import AbsNumsType, RelNumsType, ScopeNameType
2829

@@ -111,7 +112,7 @@ def _get_snames_and_nums(cpuinfo: CPUInfo.CPUInfo) -> \
111112
A tuple containing the scope name and the result of '_get_scope_nums()' for that scope name.
112113
"""
113114

114-
for sname in CPUInfo.SCOPE_NAMES:
115+
for sname in CPUInfoVars.SCOPE_NAMES:
115116
yield (sname, _get_scope_nums(sname, cpuinfo))
116117

117118
def _get_expected_topology(full_tlines: list[dict[ScopeNameType, int ]],
@@ -197,61 +198,6 @@ def _validate_topo(cpuinfo: CPUInfo.CPUInfo, exp_topo: _ExpectedTopology):
197198
f"get_compute_dies() returned {dies[pkg]} for package {pkg}, " \
198199
f"expected {exp_topo['dies'][pkg]}"
199200

200-
def _get_cpuinfos(params: CommonTestParamsTypedDict) -> Generator[CPUInfo.CPUInfo, None, None]:
201-
"""
202-
Yield 'CPUInfo' objects for testing based on the host type.
203-
204-
This generator yields CPUInfo objects with different CPU online/offline patterns:
205-
1. All CPUs online (initial state)
206-
2. Odd-numbered CPUs offline
207-
3. Even-numbered CPUs offline (excluding CPU 0)
208-
209-
Args:
210-
params: Dictionary containing test parameters.
211-
212-
Yields:
213-
'CPUInfo' objects configured according to the host type for use in tests.
214-
"""
215-
216-
pman = params["pman"]
217-
218-
with CPUInfo.CPUInfo(pman=pman) as cpuinfo:
219-
full_tlines = list(cpuinfo.get_topology())
220-
221-
# Ensure that all CPUs are online.
222-
with CPUOnline.CPUOnline(pman=pman, cpuinfo=cpuinfo) as cpuonline:
223-
cpuonline.online()
224-
225-
full_exp_topo = _get_expected_topology(full_tlines, set())
226-
227-
with CPUInfo.CPUInfo(pman=pman) as cpuinfo, \
228-
CPUOnline.CPUOnline(pman=pman, cpuinfo=cpuinfo) as cpuonline:
229-
# Pattern 0: All CPUs online
230-
yield cpuinfo
231-
_validate_topo(cpuinfo, full_exp_topo)
232-
233-
# Pattern 1: Take odd-numbered CPUs offline.
234-
all_cpus = cpuinfo.get_cpus()
235-
odd_cpus = [cpu for cpu in all_cpus if cpu % 2 == 1]
236-
237-
if odd_cpus:
238-
cpuonline.offline(cpus=odd_cpus)
239-
yield cpuinfo
240-
_validate_topo(cpuinfo, _get_expected_topology(full_tlines, set(odd_cpus)))
241-
242-
cpuonline.online(cpus=odd_cpus)
243-
_validate_topo(cpuinfo, full_exp_topo)
244-
245-
# Pattern 2: Take even-numbered CPUs offline, excluding CPU 0, which can't be offlined.
246-
even_cpus = [cpu for cpu in all_cpus if cpu % 2 == 0 and cpu != 0]
247-
if even_cpus:
248-
cpuonline.offline(cpus=even_cpus)
249-
yield cpuinfo
250-
_validate_topo(cpuinfo, _get_expected_topology(full_tlines, set(even_cpus)))
251-
252-
cpuonline.online(cpus=even_cpus)
253-
_validate_topo(cpuinfo, full_exp_topo)
254-
255201
def _run_method(name: str,
256202
cpuinfo: CPUInfo.CPUInfo,
257203
args: list | tuple | None = None,
@@ -330,7 +276,7 @@ def _test_get_good(cpuinfo: CPUInfo.CPUInfo):
330276
assert ref_nums, \
331277
f"'get_{sname}s()' is expected to return list of {sname}s, got: '{ref_nums}'"
332278

333-
for order in CPUInfo.SCOPE_NAMES:
279+
for order in CPUInfoVars.SCOPE_NAMES:
334280
nums = _get_scope_nums(sname, cpuinfo, order=order)
335281
if sname in ("die", "core"):
336282
# For dies and cores, the numbers are relative to the package numbers.
@@ -360,7 +306,7 @@ def test_cpuinfo_get(params: CommonTestParamsTypedDict):
360306
params: The test parameters.
361307
"""
362308

363-
for cpuinfo in _get_cpuinfos(params):
309+
for cpuinfo in common.get_cpuinfos(params["pman"]):
364310
_test_get_good(cpuinfo)
365311

366312
def test_cpuinfo_get_count(params: CommonTestParamsTypedDict):
@@ -371,7 +317,7 @@ def test_cpuinfo_get_count(params: CommonTestParamsTypedDict):
371317
params: The test parameters.
372318
"""
373319

374-
for cpuinfo in _get_cpuinfos(params):
320+
for cpuinfo in common.get_cpuinfos(params["pman"]):
375321
for sname, nums in _get_snames_and_nums(cpuinfo):
376322
if sname in ("core", "die"):
377323
if typing.TYPE_CHECKING:
@@ -445,7 +391,7 @@ def test_cpuinfo_convert(params: CommonTestParamsTypedDict):
445391
params: The test parameters.
446392
"""
447393

448-
for cpuinfo in _get_cpuinfos(params):
394+
for cpuinfo in common.get_cpuinfos(params["pman"]):
449395
_test_convert_good(cpuinfo)
450396

451397
def _test_normalize_good(cpuinfo: CPUInfo.CPUInfo):
@@ -512,7 +458,7 @@ def test_cpuinfo_normalize(params: CommonTestParamsTypedDict):
512458
params: The test parameters.
513459
"""
514460

515-
for cpuinfo in _get_cpuinfos(params):
461+
for cpuinfo in common.get_cpuinfos(params["pman"]):
516462
_test_normalize_good(cpuinfo)
517463

518464
def _is_globally_numbered(sname: ScopeNameType) -> bool:
@@ -657,7 +603,7 @@ def test_cpuinfo_div(params: CommonTestParamsTypedDict):
657603
params: The test parameters.
658604
"""
659605

660-
for cpuinfo in _get_cpuinfos(params):
606+
for cpuinfo in common.get_cpuinfos(params["pman"]):
661607
_test_cpuinfo_div(cpuinfo)
662608

663609
def test_core_siblings(params: CommonTestParamsTypedDict):
@@ -668,7 +614,7 @@ def test_core_siblings(params: CommonTestParamsTypedDict):
668614
params: The test parameters.
669615
"""
670616

671-
for cpuinfo in _get_cpuinfos(params):
617+
for cpuinfo in common.get_cpuinfos(params["pman"]):
672618
topology = cpuinfo.get_topology(order="core")
673619

674620
# We get the CPU siblings count for the first core in the topology list. Depending on how
@@ -720,7 +666,7 @@ def test_delayed_init(params: CommonTestParamsTypedDict):
720666

721667
# pylint: disable=protected-access
722668

723-
for cpuinfo in _get_cpuinfos(params):
669+
for cpuinfo in common.get_cpuinfos(params["pman"]):
724670
# A newly created 'CPUInfo' object should not have any topology initialized.
725671
assert "CPU" not in cpuinfo._initialized_snames, "'CPU' scope should not be initialized"
726672
assert "die" not in cpuinfo._initialized_snames, "'die' scope should not be initialized"

0 commit comments

Comments
 (0)