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>
This commit is contained in:
David Benjamin 2018-10-31 14:48:23 -05:00 committed by CQ bot account: commit-bot@chromium.org
parent 444c2e59fb
commit 6ce93ccb80
4 changed files with 106 additions and 15 deletions

View File

@ -102,6 +102,10 @@ HIDDEN uint32_t OPENSSL_armcap_P =
#else
HIDDEN uint32_t OPENSSL_armcap_P = 0;
uint32_t *OPENSSL_get_armcap_pointer_for_test(void) {
return &OPENSSL_armcap_P;
}
#endif
#endif

View File

@ -150,6 +150,14 @@ extern "C" {
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

View File

@ -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 @@ int main(int argc, char **argv) {
}
#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();
}

View File

@ -18,6 +18,7 @@ import (
"bufio"
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"math/rand"
@ -45,13 +46,19 @@ var (
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 @@ var sdeCPUs = []string{
"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 @@ func (t *testOutput) addResult(name, result string) {
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 @@ func sdeOf(cpu, path string, args ...string) *exec.Cmd {
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 @@ func runTestOnce(test test, mallocNumToFail int64) (passed bool, err error) {
}
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 @@ func main() {
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 @@ func main() {
}()
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 @@ func main() {
}
}
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 {