Simulate other ARM CPUs when running tests.
We test all Intel variants via SDE. For ARM, we can do the next best
thing and tweak with OPENSSL_armcap_P. If the host CPU does not support
the instructions we wish to test, skip it, but print something so we
know whether we need a more featureful test device.
Also fix the "CRASHED" status to "CRASH", to match
https://chromium.googlesource.com/chromium/src/+/master/docs/testing/json_test_results_format.md
(It's unclear if anything actually parses that JSON very carefully...)
Bug: 19
Change-Id: I811cc00a0d210a454287ac79c06f18fbc54f96dd
Reviewed-on: https://boringssl-review.googlesource.com/c/33204
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/crypto.c b/crypto/crypto.c
index 5f1a69a..f7ac255 100644
--- a/crypto/crypto.c
+++ b/crypto/crypto.c
@@ -102,6 +102,10 @@
#else
HIDDEN uint32_t OPENSSL_armcap_P = 0;
+
+uint32_t *OPENSSL_get_armcap_pointer_for_test(void) {
+ return &OPENSSL_armcap_P;
+}
#endif
#endif
diff --git a/crypto/internal.h b/crypto/internal.h
index a251b95..b98b556 100644
--- a/crypto/internal.h
+++ b/crypto/internal.h
@@ -150,6 +150,14 @@
void OPENSSL_cpuid_setup(void);
#endif
+#if (defined(OPENSSL_ARM) || defined(OPENSSL_AARCH64)) && \
+ !defined(OPENSSL_STATIC_ARMCAP)
+// OPENSSL_get_armcap_pointer_for_test returns a pointer to |OPENSSL_armcap_P|
+// for unit tests. Any modifications to the value must be made after
+// |CRYPTO_library_init| but before any other function call in BoringSSL.
+OPENSSL_EXPORT uint32_t *OPENSSL_get_armcap_pointer_for_test(void);
+#endif
+
#if (!defined(_MSC_VER) || defined(__clang__)) && defined(OPENSSL_64_BIT)
#define BORINGSSL_HAS_UINT128
diff --git a/crypto/test/gtest_main.cc b/crypto/test/gtest_main.cc
index 5dc8b23..4501bbb 100644
--- a/crypto/test/gtest_main.cc
+++ b/crypto/test/gtest_main.cc
@@ -12,13 +12,22 @@
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+#include <stdio.h>
#include <string.h>
#include <gtest/gtest.h>
+#include <openssl/cpu.h>
#include <openssl/rand.h>
#include "gtest_main.h"
+#include "../internal.h"
+
+#if (defined(OPENSSL_ARM) || defined(OPENSSL_AARCH64)) && \
+ !defined(OPENSSL_STATIC_ARMCAP)
+#include <openssl/arm_arch.h>
+#define TEST_ARM_CPUS
+#endif
int main(int argc, char **argv) {
@@ -33,5 +42,33 @@
}
#endif
+#if defined(TEST_ARM_CPUS)
+ for (int i = 1; i < argc; i++) {
+ if (strncmp(argv[i], "--cpu=", 6) == 0) {
+ const char *cpu = argv[i] + 6;
+ uint32_t armcap;
+ if (strcmp(cpu, "none") == 0) {
+ armcap = 0;
+ } else if (strcmp(cpu, "neon") == 0) {
+ armcap = ARMV7_NEON;
+ } else if (strcmp(cpu, "crypto") == 0) {
+ armcap = ARMV7_NEON | ARMV8_AES | ARMV8_SHA1 | ARMV8_SHA256 | ARMV8_PMULL;
+ } else {
+ fprintf(stderr, "Unknown CPU: %s\n", cpu);
+ exit(1);
+ }
+
+ uint32_t *armcap_ptr = OPENSSL_get_armcap_pointer_for_test();
+ if ((armcap & *armcap_ptr) != armcap) {
+ fprintf(stderr,
+ "Host CPU does not support features for testing CPU '%s'.\n",
+ cpu);
+ exit(89);
+ }
+ *armcap_ptr = armcap;
+ }
+ }
+#endif // TEST_ARM_CPUS
+
return RUN_ALL_TESTS();
}
diff --git a/util/all_tests.go b/util/all_tests.go
index d5794fc..8332b1f 100644
--- a/util/all_tests.go
+++ b/util/all_tests.go
@@ -18,6 +18,7 @@
"bufio"
"bytes"
"encoding/json"
+ "errors"
"flag"
"fmt"
"math/rand"
@@ -45,13 +46,19 @@
jsonOutput = flag.String("json-output", "", "The file to output JSON results to.")
mallocTest = flag.Int64("malloc-test", -1, "If non-negative, run each test with each malloc in turn failing from the given number onwards.")
mallocTestDebug = flag.Bool("malloc-test-debug", false, "If true, ask each test to abort rather than fail a malloc. This can be used with a specific value for --malloc-test to identity the malloc failing that is causing problems.")
+ simulateARMCPUs = flag.Bool("simulate-arm-cpus", simulateARMCPUsDefault(), "If true, runs tests simulating different ARM CPUs.")
)
+func simulateARMCPUsDefault() bool {
+ return runtime.GOOS == "linux" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64")
+}
+
type test struct {
args []string
shard, numShards int
- // cpu, if not empty, contains an Intel CPU code to simulate. Run
- // `sde64 -help` to get a list of these codes.
+ // cpu, if not empty, contains a code to simulate. For SDE, run `sde64
+ // -help` to get a list of these codes. For ARM, see gtest_main.cc for
+ // the supported values.
cpu string
}
@@ -100,6 +107,12 @@
"knm", // Knights Mill
}
+var armCPUs = []string{
+ "none", // No support for any ARM extensions.
+ "neon", // Support for NEON.
+ "crypto", // Support for NEON and crypto extensions.
+}
+
func newTestOutput() *testOutput {
return &testOutput{
Version: 3,
@@ -114,10 +127,14 @@
if _, found := t.Tests[name]; found {
panic(name)
}
+ expected := "PASS"
+ if result == "SKIP" {
+ expected = "SKIP"
+ }
t.Tests[name] = testResult{
Actual: result,
- Expected: "PASS",
- IsUnexpected: result != "PASS",
+ Expected: expected,
+ IsUnexpected: result != expected,
}
t.NumFailuresByType[result]++
}
@@ -178,17 +195,17 @@
return exec.Command(*sdePath, sdeArgs...)
}
-type moreMallocsError struct{}
-
-func (moreMallocsError) Error() string {
- return "child process did not exhaust all allocation calls"
-}
-
-var errMoreMallocs = moreMallocsError{}
+var (
+ errMoreMallocs = errors.New("child process did not exhaust all allocation calls")
+ errTestSkipped = errors.New("test was skipped")
+)
func runTestOnce(test test, mallocNumToFail int64) (passed bool, err error) {
prog := path.Join(*buildDir, test.args[0])
args := test.args[1:]
+ if *simulateARMCPUs && test.cpu != "" {
+ args = append([]string{"--cpu=" + test.cpu}, args...)
+ }
var cmd *exec.Cmd
if *useValgrind {
cmd = valgrindOf(false, prog, args...)
@@ -218,8 +235,12 @@
}
if err := cmd.Wait(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
- if exitError.Sys().(syscall.WaitStatus).ExitStatus() == 88 {
+ switch exitError.Sys().(syscall.WaitStatus).ExitStatus() {
+ case 88:
return false, errMoreMallocs
+ case 89:
+ fmt.Print(string(outBuf.Bytes()))
+ return false, errTestSkipped
}
}
fmt.Print(string(outBuf.Bytes()))
@@ -433,6 +454,15 @@
testForCPU.cpu = cpu
tests <- testForCPU
}
+ } else if *simulateARMCPUs {
+ // This mode is run instead of the default path,
+ // so also include the native flow.
+ tests <- test
+ for _, cpu := range armCPUs {
+ testForCPU := test
+ testForCPU.cpu = cpu
+ tests <- testForCPU
+ }
} else {
shards, err := test.getGTestShards()
if err != nil {
@@ -451,16 +481,21 @@
}()
testOutput := newTestOutput()
- var failed []test
+ var failed, skipped []test
for testResult := range results {
test := testResult.Test
args := test.args
- if testResult.Error != nil {
+ if testResult.Error == errTestSkipped {
+ fmt.Printf("%s\n", test.longName())
+ fmt.Printf("%s was skipped\n", args[0])
+ skipped = append(skipped, test)
+ testOutput.addResult(test.longName(), "SKIP")
+ } else if testResult.Error != nil {
fmt.Printf("%s\n", test.longName())
fmt.Printf("%s failed to complete: %s\n", args[0], testResult.Error)
failed = append(failed, test)
- testOutput.addResult(test.longName(), "CRASHED")
+ testOutput.addResult(test.longName(), "CRASH")
} else if !testResult.Passed {
fmt.Printf("%s\n", test.longName())
fmt.Printf("%s failed to print PASS on the last line.\n", args[0])
@@ -478,6 +513,13 @@
}
}
+ if len(skipped) > 0 {
+ fmt.Printf("\n%d of %d tests were skipped:\n", len(skipped), len(testCases))
+ for _, test := range skipped {
+ fmt.Printf("\t%s%s\n", strings.Join(test.args, " "), test.cpuMsg())
+ }
+ }
+
if len(failed) > 0 {
fmt.Printf("\n%d of %d tests failed:\n", len(failed), len(testCases))
for _, test := range failed {