From 493470e88fdc8bbbc8f8d3edb6990354560dd722 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Thu, 19 Jan 2023 07:40:31 -0800 Subject: [PATCH 01/18] README: dated example output --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54ed2b4..179565c 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ If you want to know an "explanation", specify option `--verbose`, e.g. ```sh $ x86-64-level --verbose Identified x86-64-v3, because x86-64-v4 requires 'avx512f', which -this CPU [Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz] does not support +is not supported by this CPU [Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz] 3 ``` From 69fb6d06f32b4f054d6b5013be2698207ee2accf Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Fri, 20 Jan 2023 11:52:01 -0800 Subject: [PATCH 02/18] README: never -> newer -> too new --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 179565c..cff484c 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ address 0x2b3a8b234ccd, cause 'illegal operand' This is because the older CPU does not understand one of the CPU instructions ("operands"). Note that the software might not crash each time. It will only do so if it reach the part of the code that uses -the never CPU instructions. +a too new CPU instruction. In contrast, if we compile the software on the older x86-64-v3 machine, the produced binary will only use x86-64-v3 instructions and From ed4764335e86e1fb6abab3122fae128fdccf64fc Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Sat, 4 Feb 2023 10:53:49 -0800 Subject: [PATCH 03/18] README: Clarify that _all_ CPU features must be support for a CPU to support a given level --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cff484c..1e361af 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,9 @@ microarchitecture levels]: The x86-64-v1 level is the same as the original, baseline x86-64 level. These levels are subsets of each other, i.e. x86-64-v1 ⊂ -x86-64-v2 ⊂ x86-64-v3 ⊂ x86-64-v4. - +x86-64-v2 ⊂ x86-64-v3 ⊂ x86-64-v4. For a CPU to support a level, it +must support _all_ CPU features of that version level, and, because +they are subsets of each other, all those of the lower versions. Software can be written so that they use the most powerful set of CPU features available. This optimization happens at compile time and From 0f9fd3a71cc94a25ad23dd4b72d7a9d15ae5a634 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Thu, 9 Feb 2023 11:10:32 -0800 Subject: [PATCH 04/18] README: Mention also 'Illegal instruction (core dumped)' as an example --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1e361af..951b7a5 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,12 @@ might get something like: address 0x2b3a8b234ccd, cause 'illegal operand' ``` +or + +``` +Illegal instruction (core dumped) +``` + This is because the older CPU does not understand one of the CPU instructions ("operands"). Note that the software might not crash each time. It will only do so if it reach the part of the code that uses From 73a35be6469143cb4fcbd3a6ab53188ac6972bfa Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Sat, 11 Mar 2023 00:29:09 +0100 Subject: [PATCH 05/18] ROBUSTNESS: Assert that format of CPU flags is met [#2] --- NEWS.md | 7 +++++++ x86-64-level | 31 +++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/NEWS.md b/NEWS.md index 08c786a..068a217 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,10 @@ +# Version (development version) + + * Now `x86-64-level` asserts that the input CPU flags are of the + correct format, which is assumed to be only lower-case letters, + digits, and underscores. + + # Version 0.2.1 [2023-01-18] * Now `--assert` reports also on the CPU name. diff --git a/x86-64-level b/x86-64-level index cc6d7e3..82bb768 100755 --- a/x86-64-level +++ b/x86-64-level @@ -73,14 +73,16 @@ version() { #--------------------------------------------------------------------- data= read_input() { - if $stdin; then - data=$(< /dev/stdin) - else - data=$(< /proc/cpuinfo) - fi if [[ -z ${data} ]]; then - echo >&2 "ERROR: Input data is empty" - exit 1 + if $stdin; then + data=$(< /dev/stdin) + else + data=$(< /proc/cpuinfo) + fi + if [[ -z ${data} ]]; then + echo >&2 "ERROR: Input data is empty" + exit 1 + fi fi } @@ -97,7 +99,18 @@ get_cpu_flags() { local flags flags=$(grep "^flags[[:space:]]*:" <<< "${data}" | head -n 1) flags="${flags#*:}" - echo "${flags## }" + flags="${flags## }" + if grep -v -q -E "^[[:lower:][:digit:]_ ]+$" <<< "${flags}"; then + echo >&2 "ERROR: Cannot reliably infer the CPU x86-64 level, because the format of the CPU flags comprise of other symbols than only lower-case letters, digits, and underscores: '${flags}'" + exit 1 + fi + echo "${flags}" +} + + +validate_cpu_flags() { + read_input + get_cpu_flags > /dev/null } @@ -213,6 +226,7 @@ while [[ $# -gt 0 ]]; do done if [[ -n $assert ]]; then + validate_cpu_flags version=$(report_cpu_version) if [[ $version < $assert ]]; then read_input @@ -222,5 +236,6 @@ if [[ -n $assert ]]; then exit 1 fi else + validate_cpu_flags report_cpu_version fi From d786185ceb2f4633edd1b4648ea355b4e088e51c Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Sat, 11 Mar 2023 00:30:24 +0100 Subject: [PATCH 06/18] README: Fix misleading compilation CPU target (fix #3) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 951b7a5..a131394 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ instructions ("operands"). Note that the software might not crash each time. It will only do so if it reach the part of the code that uses a too new CPU instruction. -In contrast, if we compile the software on the older x86-64-v3 +In contrast, if we compile the software towards the older x86-64-v3 machine, the produced binary will only use x86-64-v3 instructions and will therefor also run on the newer x86-64-v4 machine. From fa066cfe048260d9d48f85596ecbc139f1a09c41 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 08:26:30 -0700 Subject: [PATCH 07/18] 'head -1' -> 'head -n 1' --- x86-64-level | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x86-64-level b/x86-64-level index 82bb768..47dcf69 100755 --- a/x86-64-level +++ b/x86-64-level @@ -41,7 +41,7 @@ #' $ echo $? #' 1 #' -#' Version: 0.2.1 +#' Version: 0.2.1-9001 #' License: CC BY-SA 4.0 #' Source: https://github.com/ucsf-wynton/wynton-tools #' @@ -89,7 +89,7 @@ read_input() { get_cpu_name() { local name bfr=$(grep -E "^model name[[:space:]]*:" <<< "${data}") - name=$(echo "$bfr" | head -1) + name=$(echo "$bfr" | head -n 1) name="${name#model name*:}" echo "${name## }" } From 6732d0c9b8740e2bb9cb5452e9799e1f4ac00fe7 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 08:29:35 -0700 Subject: [PATCH 08/18] CODING STYLE: Using curly brackets around all variables consistently --- x86-64-level | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/x86-64-level b/x86-64-level index 47dcf69..3225486 100755 --- a/x86-64-level +++ b/x86-64-level @@ -41,7 +41,7 @@ #' $ echo $? #' 1 #' -#' Version: 0.2.1-9001 +#' Version: 0.2.1-9002 #' License: CC BY-SA 4.0 #' Source: https://github.com/ucsf-wynton/wynton-tools #' @@ -74,7 +74,7 @@ version() { data= read_input() { if [[ -z ${data} ]]; then - if $stdin; then + if ${stdin}; then data=$(< /dev/stdin) else data=$(< /proc/cpuinfo) @@ -89,7 +89,7 @@ read_input() { get_cpu_name() { local name bfr=$(grep -E "^model name[[:space:]]*:" <<< "${data}") - name=$(echo "$bfr" | head -n 1) + name=$(echo "${bfr}" | head -n 1) name="${name#model name*:}" echo "${name## }" } @@ -122,12 +122,12 @@ has_cpu_flags() { for flag; do ## Note, it's important to keep a trailing space case " ${flags} " in - *" $flag "*) + *" ${flag} "*) : ;; *) - if "$verbose"; then - msg="Identified x86-64-v${level}, because x86-64-v$((level + 1)) requires '$flag', which is not supported by this CPU" + if "${verbose}"; then + msg="Identified x86-64-v${level}, because x86-64-v$((level + 1)) requires '${flag}', which is not supported by this CPU" cpu_name=$(get_cpu_name) [[ -n ${cpu_name} ]] && msg="${msg} [${cpu_name}]" echo >&2 "${msg}" @@ -171,7 +171,7 @@ report_cpu_version() { exit 1 fi determine_cpu_version - echo "$level" + echo "${level}" } @@ -202,16 +202,16 @@ while [[ $# -gt 0 ]]; do key=${1//--} key=${key//=*} value=${1//--[[:alpha:]]*=} - if [[ -z $value ]]; then - merror "Option '--$key' must not be empty" + if [[ -z ${value} ]]; then + merror "Option '--${key}' must not be empty" fi - if [[ "$key" == "assert" ]]; then - assert=$value - if [[ ! $assert =~ ^-?[0-9]+$ ]]; then - echo >&2 "ERROR: Option --assert does not specify an integer: $assert" + if [[ "${key}" == "assert" ]]; then + assert=${value} + if [[ ! ${assert} =~ ^-?[0-9]+$ ]]; then + echo >&2 "ERROR: Option --assert does not specify an integer: ${assert}" exit 2 - elif [[ $assert -lt 1 ]] || [[ $assert -gt 4 ]]; then - echo >&2 "ERROR: Option --assert is out of range [1,4]: $assert" + elif [[ ${assert} -lt 1 ]] || [[ ${assert} -gt 4 ]]; then + echo >&2 "ERROR: Option --assert is out of range [1,4]: ${assert}" exit 2 fi else @@ -225,10 +225,10 @@ while [[ $# -gt 0 ]]; do shift done -if [[ -n $assert ]]; then +if [[ -n ${assert} ]]; then validate_cpu_flags version=$(report_cpu_version) - if [[ $version < $assert ]]; then + if [[ ${version} < ${assert} ]]; then read_input cpu_info=$(get_cpu_name) [[ -n ${cpu_info} ]] && cpu_info=" [${cpu_info}]" From d40f8261f230ef9a393503e063ff20baf43b6d88 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 08:31:39 -0700 Subject: [PATCH 09/18] No need to quote boolean variables + consistent use of less-than and greater-than in if-clauses --- x86-64-level | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x86-64-level b/x86-64-level index 3225486..b355010 100755 --- a/x86-64-level +++ b/x86-64-level @@ -41,7 +41,7 @@ #' $ echo $? #' 1 #' -#' Version: 0.2.1-9002 +#' Version: 0.2.1-9003 #' License: CC BY-SA 4.0 #' Source: https://github.com/ucsf-wynton/wynton-tools #' @@ -126,7 +126,7 @@ has_cpu_flags() { : ;; *) - if "${verbose}"; then + if ${verbose}; then msg="Identified x86-64-v${level}, because x86-64-v$((level + 1)) requires '${flag}', which is not supported by this CPU" cpu_name=$(get_cpu_name) [[ -n ${cpu_name} ]] && msg="${msg} [${cpu_name}]" @@ -228,7 +228,7 @@ done if [[ -n ${assert} ]]; then validate_cpu_flags version=$(report_cpu_version) - if [[ ${version} < ${assert} ]]; then + if [[ ${version} -lt ${assert} ]]; then read_input cpu_info=$(get_cpu_name) [[ -n ${cpu_info} ]] && cpu_info=" [${cpu_info}]" From b20b6c03c99963656b8915266d5c4876feb87c59 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 08:46:26 -0700 Subject: [PATCH 10/18] HELP: Typo 'x86-64-version' -> 'x86-64-level' --- x86-64-level | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x86-64-level b/x86-64-level index b355010..8cec5e3 100755 --- a/x86-64-level +++ b/x86-64-level @@ -6,7 +6,7 @@ #' i.e. x86-64-v1, x86-64-v2, x86-64-v3, or x86-64-v4. #' #' Usage: -#' x86-64-version +#' x86-64-level #' #' Options: #' --help Show this help @@ -41,7 +41,7 @@ #' $ echo $? #' 1 #' -#' Version: 0.2.1-9003 +#' Version: 0.2.1-9004 #' License: CC BY-SA 4.0 #' Source: https://github.com/ucsf-wynton/wynton-tools #' From df8e0e35c7e528bfe7d5ae12494b2ba24b69e091 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 08:57:08 -0700 Subject: [PATCH 11/18] HELP: Source: -> Source code: --- x86-64-level | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x86-64-level b/x86-64-level index 8cec5e3..37b5795 100755 --- a/x86-64-level +++ b/x86-64-level @@ -41,9 +41,9 @@ #' $ echo $? #' 1 #' -#' Version: 0.2.1-9004 +#' Version: 0.2.1-9005 #' License: CC BY-SA 4.0 -#' Source: https://github.com/ucsf-wynton/wynton-tools +#' Source code: https://github.com/ucsf-wynton/wynton-tools #' #' Authors: #' * Henrik Bengtsson (expanded on Gilles implementation [2]) From 425cf80d4f2cf92ac08b8e6b7aa107fae84779e7 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 09:36:52 -0700 Subject: [PATCH 12/18] BUG FIX: Calling `x86-64-level --assert=""` would produce error message `merror: command not found` and not the intended `ERROR: Option '--assert' must not be empty`. --- NEWS.md | 8 ++++++++ x86-64-level | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 068a217..73f0b43 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,17 @@ # Version (development version) +## New Features + * Now `x86-64-level` asserts that the input CPU flags are of the correct format, which is assumed to be only lower-case letters, digits, and underscores. +## Bug Fixes + + * Calling `x86-64-level --assert=""` would produce error message + `merror: command not found` and not the intended `ERROR: Option + '--assert' must not be empty`. + # Version 0.2.1 [2023-01-18] diff --git a/x86-64-level b/x86-64-level index 37b5795..d63ffc7 100755 --- a/x86-64-level +++ b/x86-64-level @@ -41,7 +41,7 @@ #' $ echo $? #' 1 #' -#' Version: 0.2.1-9005 +#' Version: 0.2.1-9006 #' License: CC BY-SA 4.0 #' Source code: https://github.com/ucsf-wynton/wynton-tools #' @@ -203,7 +203,8 @@ while [[ $# -gt 0 ]]; do key=${key//=*} value=${1//--[[:alpha:]]*=} if [[ -z ${value} ]]; then - merror "Option '--${key}' must not be empty" + echo >&2 "ERROR: Option '--${key}' must not be empty" + exit 2 fi if [[ "${key}" == "assert" ]]; then assert=${value} From b8cdd42f430a8853c52f60613b11118218777ba4 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 10:08:07 -0700 Subject: [PATCH 13/18] Add unit tests (fix #4) --- Makefile | 8 + NEWS.md | 4 + tests/x86-64-level.sh | 364 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 Makefile create mode 100755 tests/x86-64-level.sh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b66fd1 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +all: shellcheck check + +shellcheck: + shellcheck x86-64-level + shellcheck tests/x86-64-level.sh + +check: + @PATH=.:${PATH} tests/x86-64-level.sh diff --git a/NEWS.md b/NEWS.md index 73f0b43..80ee0f8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,10 @@ `merror: command not found` and not the intended `ERROR: Option '--assert' must not be empty`. +## Miscellaneous + + * Add unit tests. + # Version 0.2.1 [2023-01-18] diff --git a/tests/x86-64-level.sh b/tests/x86-64-level.sh new file mode 100755 index 0000000..48d2659 --- /dev/null +++ b/tests/x86-64-level.sh @@ -0,0 +1,364 @@ +#! /usr/bin/env bash + +nerrors=0 + +echo "x86-64-level ..." + +#-------------------------------------------------------------------------- +# x86-64-level --version +#-------------------------------------------------------------------------- +echo "* x86-64-level --version" +version=$(x86-64-level --version) +exit_code=$? +if [[ ${exit_code} -ne 0 ]]; then + >&2 echo "ERROR: Exit code is non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^[[:digit:]]+([.-][[:digit:]]+)+$" <<< "${version}"; then + >&2 echo "ERROR: Unexpected version string: ${version}" + nerrors=$((nerrors + 1)) +fi + +## Outputs nothing to stderr +stderr=$( { >&2 x86-64-level --version > /dev/null; } 2>&1 ) +if [[ -n ${stderr} ]]; then + >&2 echo "ERROR: Detected output to standard error: ${stderr}" + nerrors=$((nerrors + 1)) +fi + + +#-------------------------------------------------------------------------- +# x86-64-level --help +#-------------------------------------------------------------------------- +echo "* x86-64-level --help" +help=$(x86-64-level --help) +exit_code=$? +if [[ ${exit_code} -ne 0 ]]; then + >&2 echo "ERROR: Exit code is non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^Version:" <<< "${help}"; then + >&2 echo "ERROR: Help does not show version: ${help}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^License:" <<< "${help}"; then + >&2 echo "ERROR: Help does not show license: ${help}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^Source code:" <<< "${help}"; then + >&2 echo "ERROR: Help does not link to source code: ${help}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^Authors:" <<< "${help}"; then + >&2 echo "ERROR: Help does not list authors: ${help}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^Examples:" <<< "${help}"; then + >&2 echo "ERROR: Help does not show examples: ${help}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^Usage:" <<< "${help}"; then + >&2 echo "ERROR: Help does not show usage: ${help}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^Options:" <<< "${help}"; then + >&2 echo "ERROR: Help does not show options: ${help}" + nerrors=$((nerrors + 1)) +fi + +## Outputs nothing to stderr +stderr=$( { >&2 x86-64-level --help > /dev/null; } 2>&1 ) +if [[ -n ${stderr} ]]; then + >&2 echo "ERROR: Detected output to standard error: ${stderr}" + nerrors=$((nerrors + 1)) +fi + + +#-------------------------------------------------------------------------- +# x86-64-level +#-------------------------------------------------------------------------- +echo "* x86-64-level" +level=$(x86-64-level) +exit_code=$? +if [[ ${exit_code} -ne 0 ]]; then + >&2 echo "ERROR: Exit code is non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) +fi +if ! grep -q -E "^[[:digit:]]+$" <<< "${level}"; then + >&2 echo "ERROR: Non-integer x86-64 level: ${level}" + nerrors=$((nerrors + 1)) +fi +if [[ ${level} -lt 1 ]] || [[ ${level} -gt 4 ]]; then + >&2 echo "ERROR: x86-64 level out of range [1,4]: ${level}" + nerrors=$((nerrors + 1)) +fi + +## Outputs nothing to stderr +stderr=$( { >&2 x86-64-level > /dev/null; } 2>&1 ) +if [[ -n ${stderr} ]]; then + >&2 echo "ERROR: Detected output to standard error: ${stderr}" + nerrors=$((nerrors + 1)) +fi + + +#-------------------------------------------------------------------------- +# x86-64-level --assert= +#-------------------------------------------------------------------------- +echo "* x86-64-level --assert=" +level=$(x86-64-level) + +stderr=$(x86-64-level --assert="${level}" 2>&1) +exit_code=$? +if [[ ${exit_code} -ne 0 ]]; then + >&2 echo "ERROR: Exit code is non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) +fi + +## Outputs nothing to stdout +stdout=$( x86-64-level --assert="${level}"> /dev/null ) +if [[ -n ${stdout} ]]; then + >&2 echo "ERROR: Detected output to standard output: ${stdout}" + nerrors=$((nerrors + 1)) +fi + + +#-------------------------------------------------------------------------- +# x86-64-level --assert=1 +#-------------------------------------------------------------------------- +echo "* x86-64-level --assert=1" +stderr=$(x86-64-level --assert=1 2>&1) +exit_code=$? +if [[ ${exit_code} -ne 0 ]]; then + >&2 echo "ERROR: Exit code is non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) +fi + +## Outputs nothing to stdout +stdout=$( x86-64-level --assert="${level}"> /dev/null ) +if [[ -n ${stdout} ]]; then + >&2 echo "ERROR: Detected output to standard output: ${stdout}" + nerrors=$((nerrors + 1)) +fi + + + +#-------------------------------------------------------------------------- +# x86-64-level - <<< "flags: avx" +#-------------------------------------------------------------------------- +required_flags=( + "lm cmov cx8 fpu fxsr mmx syscall sse2" + "cx16 lahf_lm popcnt sse4_1 sse4_2 ssse3" + "avx avx2 bmi1 bmi2 f16c fma abm movbe xsave" + "avx512f avx512bw avx512cd avx512dq avx512vl" +) + +cpu_flags=("dummy") +for value in "${required_flags[@]}"; do + cpu_flags+=("${cpu_flags[-1]} ${value}") +done + +for truth in $(seq 0 "$((${#cpu_flags[@]} - 1))"); do + flags=${cpu_flags[${truth}]} + + echo "* x86-64-level - <<< 'flags: ${flags}'" + level=$(x86-64-level - <<< "flags: ${flags}") + exit_code=$? + if [[ ${exit_code} -ne 0 ]]; then + >&2 echo "ERROR: Exit code is non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) + fi + + if [[ "${level}" -ne "${truth}" ]]; then + >&2 echo "ERROR: Unexpected level: ${level} != ${truth}" + nerrors=$((nerrors + 1)) + fi + + ## Outputs nothing to stderr + stderr=$( { >&2 x86-64-level <<< "flags: ${flags}" > /dev/null; } 2>&1 ) + if [[ -n ${stderr} ]]; then + >&2 echo "ERROR: Detected output to standard error: ${stderr}" + nerrors=$((nerrors + 1)) + fi +done + + + + +#-------------------------------------------------------------------------- +# Exceptions +#-------------------------------------------------------------------------- +# x86-64-level --assert= +for level in -1 0 5 100; do + echo "* x86-64-level --assert=${level} (exception)" + stderr=$(x86-64-level --assert="${level}" 2>&1) + exit_code=$? + if [[ ${exit_code} -eq 0 ]]; then + >&2 echo "ERROR: Exit code should be non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) + fi + + if [[ -z ${stderr} ]]; then + >&2 echo "ERROR: No error message: '${stderr}'" + nerrors=$((nerrors + 1)) + fi + + if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then + >&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'" + nerrors=$((nerrors + 1)) + fi + + if ! grep -q -E "^ERROR: .*out of range.* ${level}" <<< "${stderr}"; then + >&2 echo "ERROR: Unexpected error message: '${stderr}'" + nerrors=$((nerrors + 1)) + fi + + ## Outputs nothing to stdout + stdout=$(x86-64-level --assert="${level}" 2> /dev/null) + if [[ -n ${stdout} ]]; then + >&2 echo "ERROR: Detected output to standard output: ${stdout}" + nerrors=$((nerrors + 1)) + fi +done + + +# x86-64-level --assert= +for level in 1.2 world; do + echo "* x86-64-level --assert=${level} (exception)" + stderr=$(x86-64-level --assert="${level}" 2>&1) + exit_code=$? + if [[ ${exit_code} -eq 0 ]]; then + >&2 echo "ERROR: Exit code should be non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) + fi + + if [[ -z ${stderr} ]]; then + >&2 echo "ERROR: No error message: '${stderr}'" + nerrors=$((nerrors + 1)) + fi + + if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then + >&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'" + nerrors=$((nerrors + 1)) + fi + + if ! grep -q -E "^ERROR: .*does not specify an integer.* ${level}" <<< "${stderr}"; then + >&2 echo "ERROR: Unexpected error message: '${stderr}'" + nerrors=$((nerrors + 1)) + fi + + ## Outputs nothing to stdout + stdout=$(x86-64-level --assert="${level}" 2> /dev/null) + if [[ -n ${stdout} ]]; then + >&2 echo "ERROR: Detected output to standard output: ${stdout}" + nerrors=$((nerrors + 1)) + fi +done + + + +# x86-64-level --assert='' +echo "* x86-64-level --assert='' (exception)" +stderr=$(x86-64-level --assert="" 2>&1) +exit_code=$? +if [[ ${exit_code} -eq 0 ]]; then + >&2 echo "ERROR: Exit code should be non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) +fi + +if [[ -z ${stderr} ]]; then + >&2 echo "ERROR: No error message: '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then + >&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +if ! grep -q -E "^ERROR: .*must not be empty" <<< "${stderr}"; then + >&2 echo "ERROR: Unexpected error message: '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +## Outputs nothing to stdout +stdout=$(x86-64-level --assert="" 2> /dev/null) +if [[ -n ${stdout} ]]; then + >&2 echo "ERROR: Detected output to standard output: ${stdout}" + nerrors=$((nerrors + 1)) +fi + + +echo "* x86-64-level - <<< '' (exception)" +stderr=$(x86-64-level - <<< '' 2>&1) +exit_code=$? +if [[ ${exit_code} -eq 0 ]]; then + >&2 echo "ERROR: Exit code is not non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) +fi + +if [[ -z ${stderr} ]]; then + >&2 echo "ERROR: No error message: '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then + >&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +if ! grep -q -E "^ERROR: .*Input data is empty" <<< "${stderr}"; then + >&2 echo "ERROR: Unexpected error message: '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +## Outputs nothing to stdout +stdout=$( x86-64-level - <<< '' 2> /dev/null ) +if [[ -n ${stdout} ]]; then + >&2 echo "ERROR: Detected output to standard output: ${stdout}" + nerrors=$((nerrors + 1)) +fi + + +echo "* x86-64-level - <<< 'flags: AVX' (exception)" +stderr=$(x86-64-level - <<< 'flags: AVX' 2>&1) +exit_code=$? +if [[ ${exit_code} -eq 0 ]]; then + >&2 echo "ERROR: Exit code is not non-zero: ${exit_code}" + nerrors=$((nerrors + 1)) +fi + +if [[ -z ${stderr} ]]; then + >&2 echo "ERROR: No error message: '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +if ! head -n 1 <<< "${stderr}" | grep -q -E "^ERROR:"; then + >&2 echo "ERROR: Standard error output does not begin with 'ERROR:': '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +if ! grep -q -E "^ERROR: .*format of the CPU flags" <<< "${stderr}"; then + >&2 echo "ERROR: Unexpected error message: '${stderr}'" + nerrors=$((nerrors + 1)) +fi + +## Outputs nothing to stdout +stdout=$( x86-64-level - <<< 'flags: AVX' 2> /dev/null ) +if [[ -n ${stdout} ]]; then + >&2 echo "ERROR: Detected output to standard output: ${stdout}" + nerrors=$((nerrors + 1)) +fi + + + +#-------------------------------------------------------------------------- +# Summary +#-------------------------------------------------------------------------- +if [[ ${nerrors} -eq 0 ]]; then + echo "x86-64-level ... DONE" +else + echo "Number of ERRORS: ${nerrors}" + echo "x86-64-level ... ERROR" + exit 1 +fi + From 6aec50ed150233365ddda4158fbfc524a75e76c3 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 10:10:21 -0700 Subject: [PATCH 14/18] GHA: Run unit tests [#5] --- .github/workflows/check.yml | 24 ++++++++++++++++++++++++ .github/workflows/shellcheck.yml | 1 + 2 files changed, 25 insertions(+) create mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..09acbb7 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,24 @@ +on: [push, pull_request] + +name: "unit tests" + +jobs: + checks: + if: "! contains(github.event.head_commit.message, '[ci skip]')" + + timeout-minutes: 2 + + runs-on: ubuntu-22.04 + + name: "unit tests" + + strategy: + fail-fast: false + + steps: + - name: Checkout git repository + uses: actions/checkout@v3 + + - name: Run unit tests + run: | + PATH=.:$PATH tests/x86-64-level.sh diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 6d0186f..dba56bb 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -32,3 +32,4 @@ jobs: - name: ShellCheck run: | ./shellcheck -- x86-64-level + ./shellcheck -- tests/x86-64-level.sh From 5c038bd1a54a2bc8693d8a4501a558f490b4e21d Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 10:12:19 -0700 Subject: [PATCH 15/18] GHA: Run unit tests - take 2 [#5] --- .github/workflows/{check.yml => unit_tests.yml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{check.yml => unit_tests.yml} (89%) diff --git a/.github/workflows/check.yml b/.github/workflows/unit_tests.yml similarity index 89% rename from .github/workflows/check.yml rename to .github/workflows/unit_tests.yml index 09acbb7..f92dfe4 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/unit_tests.yml @@ -1,6 +1,6 @@ on: [push, pull_request] -name: "unit tests" +name: unit_tests jobs: checks: @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-22.04 - name: "unit tests" + name: unit_tests strategy: fail-fast: false From cd09344f39435ca0c9b6d7a2da81a660aed4f37c Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Tue, 25 Apr 2023 10:14:21 -0700 Subject: [PATCH 16/18] README: Add 'unit tests' badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a131394..5e8a9f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![shellcheck](https://github.com/HenrikBengtsson/x86-64-level/actions/workflows/shellcheck.yml/badge.svg)](https://github.com/HenrikBengtsson/x86-64-level/actions/workflows/shellcheck.yml) +[![unit_tests](https://github.com/HenrikBengtsson/x86-64-level/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/HenrikBengtsson/x86-64-level/actions/workflows/unit_tests.yml) # x86-64-level - Get the x86-64 Microarchitecture Level on the Current Machine From c079f3f571b2783ba26592518419c3fdcfc927c5 Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Thu, 25 May 2023 09:37:03 -0700 Subject: [PATCH 17/18] README: grammar fixes --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5e8a9f1..235c8f6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # x86-64-level - Get the x86-64 Microarchitecture Level on the Current Machine -TL;DR: The `x86-64-level` tool identifies the current CPU supports +TL;DR: The `x86-64-level` tool identifies if the current CPU supports x86-64-v1, x86-64-v2, x86-64-v3, or x86-64-v4, e.g. ```sh @@ -15,7 +15,7 @@ $ x86-64-level # Background **x86-64** is a 64-bit version of the x86 CPU instruction set -supported by AMD and Intel CPUs among others. Since the first +supported by AMD and Intel CPUs, among others. Since the first generations of CPUs, more low-level CPU features have been added over the years. The x86-64 CPU features can be grouped into four [CPU microarchitecture levels]: @@ -53,9 +53,9 @@ Illegal instruction (core dumped) ``` This is because the older CPU does not understand one of the CPU -instructions ("operands"). Note that the software might not crash each -time. It will only do so if it reach the part of the code that uses -a too new CPU instruction. +instructions ("operands"). Note that the software might not crash +each time. It will only do so if it reaches the part of the code that +uses a CPU instruction that is not recognized by the current CPU. In contrast, if we compile the software towards the older x86-64-v3 machine, the produced binary will only use x86-64-v3 instructions and @@ -73,8 +73,8 @@ illegal operation' problem. ## Finding CPU's x86-64 level -This tool, `x86-64-level`, allows you to query the which x86-64 level -the CPU on current machine supports. For example, +This tool, `x86-64-level`, allows you to query which x86-64 level the +CPU on current machine supports. For example, ```sh $ x86-64-level @@ -89,7 +89,8 @@ $ echo "x86-64-v${level}" x86-64-v3 ``` -If you want to know an "explanation", specify option `--verbose`, e.g. +If you want to get an explanation for the identified level, specify +option `--verbose`, e.g. ```sh $ x86-64-level --verbose @@ -131,7 +132,7 @@ x86-64-level --assert=4 || exit 1 This will output that error message (to the standard error) and exit the script with exit code 1, if, and only if, the current machine does -not support x86-64-v4. In all other cases, it continue silently. +not support x86-64-v4. In all other cases, it continues silently. From 9e6b2b41be098ac502c88a756e9483f70f7a5b6f Mon Sep 17 00:00:00 2001 From: Henrik Bengtsson Date: Thu, 25 May 2023 09:39:32 -0700 Subject: [PATCH 18/18] x86-64-level 0.2.2 --- NEWS.md | 2 +- x86-64-level | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 80ee0f8..b7e94e9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# Version (development version) +# Version 0.2.2 [2023-05-25] ## New Features diff --git a/x86-64-level b/x86-64-level index d63ffc7..25343cc 100755 --- a/x86-64-level +++ b/x86-64-level @@ -41,7 +41,7 @@ #' $ echo $? #' 1 #' -#' Version: 0.2.1-9006 +#' Version: 0.2.2 #' License: CC BY-SA 4.0 #' Source code: https://github.com/ucsf-wynton/wynton-tools #'