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 {