diff --git a/ChangeLog b/ChangeLog index 59d8633..26af854 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,5 @@ * Release 8.1.0 + * Fixed Bug #152: CPU stepping, model, and family values are blank if 0 * Fixed Bug #177: Officially drop support for Python 2 * Fixed Bug #171: Replace Python 3.11 deprecated unittest.makeSuite * Fixed Bug #173: Fix lgtm.com alerts diff --git a/cpuinfo/cpuinfo.py b/cpuinfo/cpuinfo.py index b00dd32..1a3ce9d 100644 --- a/cpuinfo/cpuinfo.py +++ b/cpuinfo/cpuinfo.py @@ -874,18 +874,36 @@ def _is_selinux_enforcing(trace): return (not can_selinux_exec_heap or not can_selinux_exec_memory) -def _filter_dict_keys_with_empty_values(info): - # Filter out None, 0, "", (), {}, [] - info = {k: v for k, v in info.items() if v} +def _filter_dict_keys_with_empty_values(info, acceptable_values = {}): + filtered_info = {} + for key in info: + value = info[key] - # Filter out (0, 0) - info = {k: v for k, v in info.items() if v != (0, 0)} + # Keep if value is acceptable + if key in acceptable_values: + if acceptable_values[key] == value: + filtered_info[key] = value + continue - # Filter out strings that start with "0.0" - info = {k: v for k, v in info.items() if not (type(v) == str and v.startswith('0.0'))} + # Filter out None, 0, "", (), {}, [] + if not value: + continue - return info + # Filter out (0, 0) + if value == (0, 0): + continue + + # Filter out -1 + if value == -1: + continue + + # Filter out strings that start with "0.0" + if type(value) == str and value.startswith('0.0'): + continue + filtered_info[key] = value + + return filtered_info class ASM(object): def __init__(self, restype=None, argtypes=(), machine_code=[]): @@ -1710,9 +1728,9 @@ def _get_cpu_info_from_proc_cpuinfo(): vendor_id = _get_field(False, output, None, '', 'vendor_id', 'vendor id', 'vendor') processor_brand = _get_field(True, output, None, None, 'model name', 'cpu', 'processor', 'uarch') cache_size = _get_field(False, output, None, '', 'cache size') - stepping = _get_field(False, output, int, 0, 'stepping') - model = _get_field(False, output, int, 0, 'model') - family = _get_field(False, output, int, 0, 'cpu family') + stepping = _get_field(False, output, int, -1, 'stepping') + model = _get_field(False, output, int, -1, 'model') + family = _get_field(False, output, int, -1, 'cpu family') hardware = _get_field(False, output, None, '', 'Hardware') # Flags @@ -1775,7 +1793,7 @@ def _get_cpu_info_from_proc_cpuinfo(): info['hz_actual_friendly'] = _hz_short_to_friendly(hz_actual, 6) info['hz_actual'] = _hz_short_to_full(hz_actual, 6) - info = _filter_dict_keys_with_empty_values(info) + info = _filter_dict_keys_with_empty_values(info, {'stepping':0, 'model':0, 'family':0}) g_trace.success() return info except Exception as err: @@ -1915,7 +1933,7 @@ def _get_cpu_info_from_lscpu(): flags.sort() info['flags'] = flags - info = _filter_dict_keys_with_empty_values(info) + info = _filter_dict_keys_with_empty_values(info, {'stepping':0, 'model':0, 'family':0}) g_trace.success() return info except Exception as err: diff --git a/test_suite.py b/test_suite.py index 2458e7a..08c03dc 100644 --- a/test_suite.py +++ b/test_suite.py @@ -75,6 +75,8 @@ from test_cpuid import TestCPUID from test_actual import TestActual from test_cli import TestCLI +from test_bug_152_cpu_zero import TestBug152 +from test_filter import TestFilter if __name__ == '__main__': def logger(msg): @@ -123,7 +125,9 @@ def logger(msg): TestWindows_10_X86_64_Ryzen7, TestCPUID, TestActual, - TestCLI + TestCLI, + TestBug152, + TestFilter, ] # Add the tests to the suite diff --git a/tests/test_bug_152_cpu_zero.py b/tests/test_bug_152_cpu_zero.py new file mode 100644 index 0000000..1c66b5a --- /dev/null +++ b/tests/test_bug_152_cpu_zero.py @@ -0,0 +1,126 @@ + + +import unittest +from cpuinfo import * +import helpers + + +class MockDataSource(object): + bits = '64bit' + cpu_count = 1 + is_windows = False + arch_string_raw = 'x86_64' + uname_string_raw = 'x86_64' + can_cpuid = False + + @staticmethod + def has_proc_cpuinfo(): + return True + + @staticmethod + def has_lscpu(): + return True + + @staticmethod + def cat_proc_cpuinfo(): + returncode = 0 + output = r''' +processor : 0 +vendor_id : GenuineIntel +cpu family : 0 +model : 0 +model name : Intel(R) Pentium(R) CPU G640 @ 2.80GHz +stepping : 0 +microcode : 0x29 +cpu MHz : 1901.375 +cache size : 3072 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 2 +apicid : 0 +initial apicid : 0 +fpu : yes +fpu_exception : yes +cpuid level : 13 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 popcnt tsc_deadline_timer xsave lahf_lm epb tpr_shadow vnmi flexpriority ept vpid xsaveopt dtherm arat pln pts +bugs : +bogomips : 5587.32 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: + + +''' + return returncode, output + + @staticmethod + def lscpu(): + returncode = 0 + output = r''' +Architecture: x86_64 +CPU op-mode(s): 32-bit, 64-bit +Byte Order: Little Endian +CPU(s): 2 +On-line CPU(s) list: 0,1 +Thread(s) per core: 1 +Core(s) per socket: 2 +Socket(s): 1 +NUMA node(s): 1 +Vendor ID: GenuineIntel +CPU family: 0 +Model: 0 +Model name: Intel(R) Pentium(R) CPU G640 @ 2.80GHz +Stepping: 0 +CPU MHz: 2070.796 +CPU max MHz: 2800.0000 +CPU min MHz: 1600.0000 +BogoMIPS: 5587.32 +Virtualization: VT-x +L1d cache: 32K +L1i cache: 32K +L2 cache: 256K +L3 cache: 3072K +NUMA node0 CPU(s): 0,1 +Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 popcnt tsc_deadline_timer xsave lahf_lm epb tpr_shadow vnmi flexpriority ept vpid xsaveopt dtherm arat pln pts + + +''' + return returncode, output + +# Confirms fix for: https://github.com/workhorsy/py-cpuinfo/issues/152 +# CPU stepping, model, and family values are blank if 0 +class TestBug152(unittest.TestCase): + def setUp(self): + helpers.backup_data_source(cpuinfo) + helpers.monkey_patch_data_source(cpuinfo, MockDataSource) + + def tearDown(self): + helpers.restore_data_source(cpuinfo) + + def test_get_cpu_info_from_lscpu(self): + info = cpuinfo._get_cpu_info_from_lscpu() + + # Make sure fields with 0 are not filtered out + self.assertIn('stepping', info.keys()) + self.assertIn('model', info.keys()) + self.assertIn('family', info.keys()) + + self.assertEqual(0, info['stepping']) + self.assertEqual(0, info['model']) + self.assertEqual(0, info['family']) + + def test_get_cpu_info_from_proc_cpuinfo(self): + info = cpuinfo._get_cpu_info_from_proc_cpuinfo() + + # Make sure fields with 0 are not filtered out + self.assertIn('stepping', info.keys()) + self.assertIn('model', info.keys()) + self.assertIn('family', info.keys()) + + self.assertEqual(0, info['stepping']) + self.assertEqual(0, info['model']) + self.assertEqual(0, info['family']) + diff --git a/tests/test_filter.py b/tests/test_filter.py new file mode 100644 index 0000000..dd56d50 --- /dev/null +++ b/tests/test_filter.py @@ -0,0 +1,30 @@ + + +import unittest +from cpuinfo import * +import helpers + + +class TestFilter(unittest.TestCase): + def test_filter(self): + # Make sure NON empty values DON'T get filtered out + for x in [2, "abc", ("one"), {"key" : "val"}, [9, 8, 7]]: + info = { 'example' : x } + info = cpuinfo._filter_dict_keys_with_empty_values(info) + self.assertEqual(info, { 'example' : x }) + + # Make sure empty values get filtered out + for x in [None, 0, -1, "", (), {}, [], (0, 0), "0.0"]: + info = { 'example' : x } + info = cpuinfo._filter_dict_keys_with_empty_values(info) + self.assertEqual(info, {}) + + # Make sure 0 values get filtered out + info = { 'aaa':1, 'bbb':0, 'ccc':2 } + info = cpuinfo._filter_dict_keys_with_empty_values(info) + self.assertEqual(info, { 'aaa':1, 'ccc':2 }) + + # Make sure 0 values dont get filtered out with 0 as acceptable value + info = { 'aaa':1, 'bbb':0, 'ccc':2 } + info = cpuinfo._filter_dict_keys_with_empty_values(info, { 'bbb' : 0 }) + self.assertEqual(info, { 'aaa':1, 'bbb':0, 'ccc':2 })