Skip to content

Commit

Permalink
Integration test for CPU feature detection (#1186)
Browse files Browse the repository at this point in the history
**Issue:**
I accidentally committed broken CPU-feature-detection code in #1182 (fixed the next day by #1184)

This kind of accident slipped through because I was in a rush, and we don't have unit tests to double-check this particular code. Unit tests for feature detection are hard because these features vary wildly machine to machine.

**Description of changes:**
Add an integration test that checks CPU-feature-detection against linux's easy-to-parse `/proc/cpuinfo`.

This test is skipped on platforms like Windows and Apple where there's no simple way to query CPU features
  • Loading branch information
graebm authored Jan 31, 2025
1 parent 0e7637f commit bfa6c85
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,6 @@ cmake-build*
# CBMC files
*.goto
*.log

# source code for system_info and other test apps
!bin
6 changes: 6 additions & 0 deletions bin/system_info/print_system_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ int main(void) {
fprintf(stdout, " 'amd_bmi2': %s\n", aws_cpu_has_feature(AWS_CPU_FEATURE_BMI2) ? "true" : "false");
fprintf(stdout, " }\n");

#if defined(AWS_USE_CPU_EXTENSIONS)
fprintf(stdout, " 'use cpu extensions': true\n");
#else
fprintf(stdout, " 'use cpu extensions': false\n");
#endif

fprintf(stdout, "}\n");
aws_system_environment_release(env);
aws_logger_clean_up(&logger);
Expand Down
159 changes: 159 additions & 0 deletions bin/system_info/test-print-system-info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
import argparse
import os
from pathlib import Path
import platform
import re
import subprocess
from typing import Dict, List, Optional

PARSER = argparse.ArgumentParser(
description="Run print-sys-info to detect CPU features. Fail if the results seem wrong.")
PARSER.add_argument("--print-sys-info-path",
help="Path to print-sys-info app")
PARSER.add_argument("--build-dir",
help="Search this dir for print-sys-info app")


FEATURE_ALIASES = {
'avx512': ['avx512f'],
'clmul': ['pclmulqdq'],
'crc': ['crc32'],
'crypto': ['aes'],
}


def main():
args = PARSER.parse_args()

if args.print_sys_info_path:
app = Path(args.print_sys_info_path)
else:
app = find_app(args.build_dir)

# run print-sys-info. get feature names and whether it thinks they're supported
app_features_presence: Dict[str, bool] = detect_features_from_app(app)

# get feature info from OS (i.e. read /proc/cpuinfo on linux), get back list of supported features
os_features_list = detect_features_from_os()

# For each feature that print-sys-info says was (or wasn't) there,
# check the os_features_list and make sure it is (or isn't_ present.
for (feature, app_presence) in app_features_presence.items():
os_presence = feature in os_features_list
if not os_presence:
# sometimes a feature has a mildly different name across platforms
for alias in FEATURE_ALIASES.get(feature, []):
if alias in os_features_list:
os_presence = True
break

if app_presence != os_presence:
exit(f"FAILED: aws-c-common and OS disagree on whether CPU supports feature '{feature}'\n" +
f"\taws-c-common:{app_presence} OS:{os_presence}")

print("SUCCESS: aws-c-common and OS agree on CPU features")


def find_app(build_dir: Optional[str]) -> Path:
if build_dir is None:
build_dir = find_build_dir()
else:
build_dir = Path(build_dir).absolute()

app_name = 'print-sys-info'
if os.name == 'nt':
app_name = app_name + '.exe'

for file in build_dir.glob(f"**/{app_name}"):
return file

exit(f"FAILED: Can't find '{app_name}' under: {build_dir}"
"\nPass --build-dir to hint location.")


def find_build_dir() -> Path:
dir = Path(__file__).parent.absolute()
while dir is not None:
for build_dir in dir.glob('build'):
return build_dir
dir = dir.parent

exit("FAILED: Can't find build dir. Pass --build-dir to hint location.")


def detect_features_from_app(app_path: Path) -> Dict[str, bool]:
result = subprocess.run([str(app_path)],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
print(f"--- {app_path} ---")
print(result.stderr)
print(result.stdout)
if result.returncode != 0:
exit(f"FAILED: {app_path.name} exited with {result.returncode}")

lines = result.stdout.splitlines()

machine = platform.machine().lower()
if machine in ['x86_64', 'amd64']:
machine = 'amd'
elif machine.startswith('arm') or machine == 'aarch64':
machine = 'arm'
else:
print(f"SKIP TEST: unknown platform.machine(): {machine}")
exit(0)

# looking for lines like:
# 'arm_crypto': true,
# 'amd_sse4_1': false
features = {}
for line in lines:
m = re.search(f"'{machine}_(.+)': (false|true)", line)
if m:
name = m.group(1)
is_present = m.group(2) == 'true'
features[name] = is_present

# if aws-c-common compiled with -DUSE_CPU_EXTENSIONS=OFF, skip this this test
for line in lines:
m = re.search(f"'use cpu extensions': false", line)
if m:
print("SKIP TEST: aws-c-common compiled with -DUSE_CPU_EXTENSIONS=OFF")
exit(0)

if not features:
raise RuntimeError("Cannot find features text in stdout ???")

return features


def detect_features_from_os() -> List[str]:
features = []

cpuinfo_path = '/proc/cpuinfo'
try:
with open(cpuinfo_path) as f:
cpuinfo_text = f.read()
except:
print(f"SKIP TEST: currently, this test only works on machines with /proc/cpuinfo")
exit(0)

# looking for line like:
# flags : fpu vme de pse ...
# OR
# features : fp asimd evtstrm ...
print(f"--- {cpuinfo_path} ---")
for line in cpuinfo_text.splitlines():
line = line.lower()
print(line)
m = re.match(r"(flags|features)\s+:(.*)", line)
if m:
features = m.group(2).split()
return features

exit(f"FAILED: Cannot detect CPU features in {cpuinfo_path}")


if __name__ == '__main__':
main()
6 changes: 5 additions & 1 deletion builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@
["dir", "/s", "/b"]
]
}
}
},
"test_steps": [
"test",
["python3", "{source_dir}/bin/system_info/test-print-system-info.py", "--build-dir", "{build_dir}"]
]
}

0 comments on commit bfa6c85

Please sign in to comment.