1// Copyright (c) 2016, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15package main 16 17import ( 18 "bytes" 19 "encoding/json" 20 "flag" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "strconv" 28 "strings" 29) 30 31var ( 32 buildDir = flag.String("build-dir", "build", "Specifies the build directory to push.") 33 adbPath = flag.String("adb", "adb", "Specifies the adb binary to use. Defaults to looking in PATH.") 34 device = flag.String("device", "", "Specifies the device or emulator. See adb's -s argument.") 35 aarch64 = flag.Bool("aarch64", false, "Build the test runners for aarch64 instead of arm.") 36 arm = flag.Int("arm", 7, "Which arm revision to build for.") 37 suite = flag.String("suite", "all", "Specifies the test suites to run (all, unit, or ssl).") 38 allTestsArgs = flag.String("all-tests-args", "", "Specifies space-separated arguments to pass to all_tests.go") 39 runnerArgs = flag.String("runner-args", "", "Specifies space-separated arguments to pass to ssl/test/runner") 40 jsonOutput = flag.String("json-output", "", "The file to output JSON results to.") 41) 42 43func enableUnitTests() bool { 44 return *suite == "all" || *suite == "unit" 45} 46 47func enableSSLTests() bool { 48 return *suite == "all" || *suite == "ssl" 49} 50 51func adb(args ...string) error { 52 if len(*device) > 0 { 53 args = append([]string{"-s", *device}, args...) 54 } 55 cmd := exec.Command(*adbPath, args...) 56 cmd.Stdout = os.Stdout 57 cmd.Stderr = os.Stderr 58 return cmd.Run() 59} 60 61func adbShell(shellCmd string) (int, error) { 62 var args []string 63 if len(*device) > 0 { 64 args = append([]string{"-s", *device}, args...) 65 } 66 args = append(args, "shell") 67 68 const delimiter = "___EXIT_CODE___" 69 70 // Older versions of adb and Android do not preserve the exit 71 // code, so work around this. 72 // https://code.google.com/p/android/issues/detail?id=3254 73 shellCmd += "; echo " + delimiter + " $?" 74 args = append(args, shellCmd) 75 76 cmd := exec.Command(*adbPath, args...) 77 stdout, err := cmd.StdoutPipe() 78 if err != nil { 79 return 0, err 80 } 81 cmd.Stderr = os.Stderr 82 if err := cmd.Start(); err != nil { 83 return 0, err 84 } 85 86 var stdoutBytes bytes.Buffer 87 for { 88 var buf [1024]byte 89 n, err := stdout.Read(buf[:]) 90 stdoutBytes.Write(buf[:n]) 91 os.Stdout.Write(buf[:n]) 92 if err != nil { 93 break 94 } 95 } 96 97 if err := cmd.Wait(); err != nil { 98 return 0, err 99 } 100 101 stdoutStr := stdoutBytes.String() 102 idx := strings.LastIndex(stdoutStr, delimiter) 103 if idx < 0 { 104 return 0, fmt.Errorf("Could not find delimiter in output.") 105 } 106 107 return strconv.Atoi(strings.TrimSpace(stdoutStr[idx+len(delimiter):])) 108} 109 110func goTool(args ...string) error { 111 cmd := exec.Command("go", args...) 112 cmd.Stdout = os.Stdout 113 cmd.Stderr = os.Stderr 114 115 cmd.Env = os.Environ() 116 if *aarch64 { 117 cmd.Env = append(cmd.Env, "GOARCH=arm64") 118 } else { 119 cmd.Env = append(cmd.Env, "GOARCH=arm") 120 cmd.Env = append(cmd.Env, fmt.Sprintf("GOARM=%d", *arm)) 121 } 122 return cmd.Run() 123} 124 125// setWorkingDirectory walks up directories as needed until the current working 126// directory is the top of a BoringSSL checkout. 127func setWorkingDirectory() { 128 for i := 0; i < 64; i++ { 129 if _, err := os.Stat("BUILDING.md"); err == nil { 130 return 131 } 132 os.Chdir("..") 133 } 134 135 panic("Couldn't find BUILDING.md in a parent directory!") 136} 137 138type test struct { 139 args []string 140 env []string 141} 142 143func parseTestConfig(filename string) ([]test, error) { 144 in, err := os.Open(filename) 145 if err != nil { 146 return nil, err 147 } 148 defer in.Close() 149 150 decoder := json.NewDecoder(in) 151 var result [][]string 152 if err := decoder.Decode(&result); err != nil { 153 return nil, err 154 } 155 156 tests := make([]test, 0, len(result)) 157 for _, args := range result { 158 var env []string 159 for len(args) > 0 && strings.HasPrefix(args[0], "$") { 160 env = append(env, args[0][1:]) 161 args = args[1:] 162 } 163 tests = append(tests, test{args: args, env: env}) 164 } 165 166 return tests, nil 167} 168 169func copyFile(dst, src string) error { 170 srcFile, err := os.Open(src) 171 if err != nil { 172 return err 173 } 174 defer srcFile.Close() 175 176 srcInfo, err := srcFile.Stat() 177 if err != nil { 178 return err 179 } 180 181 dir := filepath.Dir(dst) 182 if err := os.MkdirAll(dir, 0777); err != nil { 183 return err 184 } 185 186 dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, srcInfo.Mode()) 187 if err != nil { 188 return err 189 } 190 defer dstFile.Close() 191 192 _, err = io.Copy(dstFile, srcFile) 193 return err 194} 195 196func main() { 197 flag.Parse() 198 199 if *suite == "all" && *jsonOutput != "" { 200 fmt.Printf("To use -json-output flag, select only one test suite with -suite.\n") 201 os.Exit(1) 202 } 203 204 setWorkingDirectory() 205 206 // Clear the target directory. 207 if err := adb("shell", "rm -Rf /data/local/tmp/boringssl-tmp"); err != nil { 208 fmt.Printf("Failed to clear target directory: %s\n", err) 209 os.Exit(1) 210 } 211 212 // Stage everything in a temporary directory. 213 tmpDir, err := ioutil.TempDir("", "boringssl-android") 214 if err != nil { 215 fmt.Printf("Error making temporary directory: %s\n", err) 216 os.Exit(1) 217 } 218 defer os.RemoveAll(tmpDir) 219 220 var binaries, files []string 221 222 if enableUnitTests() { 223 files = append(files, 224 "util/all_tests.json", 225 "BUILDING.md", 226 ) 227 228 tests, err := parseTestConfig("util/all_tests.json") 229 if err != nil { 230 fmt.Printf("Failed to parse input: %s\n", err) 231 os.Exit(1) 232 } 233 234 seenBinary := make(map[string]struct{}) 235 for _, test := range tests { 236 if _, ok := seenBinary[test.args[0]]; !ok { 237 binaries = append(binaries, test.args[0]) 238 seenBinary[test.args[0]] = struct{}{} 239 } 240 for _, arg := range test.args[1:] { 241 if strings.Contains(arg, "/") { 242 files = append(files, arg) 243 } 244 } 245 } 246 247 fmt.Printf("Building all_tests...\n") 248 if err := goTool("build", "-o", filepath.Join(tmpDir, "util/all_tests"), "util/all_tests.go"); err != nil { 249 fmt.Printf("Error building all_tests.go: %s\n", err) 250 os.Exit(1) 251 } 252 } 253 254 if enableSSLTests() { 255 binaries = append(binaries, "ssl/test/bssl_shim") 256 files = append(files, 257 "BUILDING.md", 258 "ssl/test/runner/cert.pem", 259 "ssl/test/runner/channel_id_key.pem", 260 "ssl/test/runner/ecdsa_p224_cert.pem", 261 "ssl/test/runner/ecdsa_p224_key.pem", 262 "ssl/test/runner/ecdsa_p256_cert.pem", 263 "ssl/test/runner/ecdsa_p256_key.pem", 264 "ssl/test/runner/ecdsa_p384_cert.pem", 265 "ssl/test/runner/ecdsa_p384_key.pem", 266 "ssl/test/runner/ecdsa_p521_cert.pem", 267 "ssl/test/runner/ecdsa_p521_key.pem", 268 "ssl/test/runner/ed25519_cert.pem", 269 "ssl/test/runner/ed25519_key.pem", 270 "ssl/test/runner/key.pem", 271 "ssl/test/runner/rsa_1024_cert.pem", 272 "ssl/test/runner/rsa_1024_key.pem", 273 "ssl/test/runner/rsa_chain_cert.pem", 274 "ssl/test/runner/rsa_chain_key.pem", 275 "util/all_tests.json", 276 ) 277 278 fmt.Printf("Building runner...\n") 279 if err := goTool("test", "-c", "-o", filepath.Join(tmpDir, "ssl/test/runner/runner"), "./ssl/test/runner/"); err != nil { 280 fmt.Printf("Error building runner: %s\n", err) 281 os.Exit(1) 282 } 283 } 284 285 var libraries []string 286 if _, err := os.Stat(filepath.Join(*buildDir, "crypto/libcrypto.so")); err == nil { 287 libraries = []string{ 288 "libboringssl_gtest.so", 289 "crypto/libcrypto.so", 290 "decrepit/libdecrepit.so", 291 "ssl/libssl.so", 292 } 293 } else if !os.IsNotExist(err) { 294 fmt.Printf("Failed to stat crypto/libcrypto.so: %s\n", err) 295 os.Exit(1) 296 } 297 298 fmt.Printf("Copying test binaries...\n") 299 for _, binary := range binaries { 300 if err := copyFile(filepath.Join(tmpDir, "build", binary), filepath.Join(*buildDir, binary)); err != nil { 301 fmt.Printf("Failed to copy %s: %s\n", binary, err) 302 os.Exit(1) 303 } 304 } 305 306 var envPrefix string 307 if len(libraries) > 0 { 308 fmt.Printf("Copying libraries...\n") 309 for _, library := range libraries { 310 // Place all the libraries in a common directory so they 311 // can be passed to LD_LIBRARY_PATH once. 312 if err := copyFile(filepath.Join(tmpDir, "build", "lib", filepath.Base(library)), filepath.Join(*buildDir, library)); err != nil { 313 fmt.Printf("Failed to copy %s: %s\n", library, err) 314 os.Exit(1) 315 } 316 } 317 envPrefix = "env LD_LIBRARY_PATH=/data/local/tmp/boringssl-tmp/build/lib " 318 } 319 320 fmt.Printf("Copying data files...\n") 321 for _, file := range files { 322 if err := copyFile(filepath.Join(tmpDir, file), file); err != nil { 323 fmt.Printf("Failed to copy %s: %s\n", file, err) 324 os.Exit(1) 325 } 326 } 327 328 fmt.Printf("Uploading files...\n") 329 if err := adb("push", "-p", tmpDir, "/data/local/tmp/boringssl-tmp"); err != nil { 330 fmt.Printf("Failed to push runner: %s\n", err) 331 os.Exit(1) 332 } 333 334 var unitTestExit int 335 if enableUnitTests() { 336 fmt.Printf("Running unit tests...\n") 337 unitTestExit, err = adbShell(fmt.Sprintf("cd /data/local/tmp/boringssl-tmp && %s./util/all_tests -json-output results.json %s", envPrefix, *allTestsArgs)) 338 if err != nil { 339 fmt.Printf("Failed to run unit tests: %s\n", err) 340 os.Exit(1) 341 } 342 } 343 344 var sslTestExit int 345 if enableSSLTests() { 346 fmt.Printf("Running SSL tests...\n") 347 sslTestExit, err = adbShell(fmt.Sprintf("cd /data/local/tmp/boringssl-tmp/ssl/test/runner && %s./runner -json-output ../../../results.json %s", envPrefix, *runnerArgs)) 348 if err != nil { 349 fmt.Printf("Failed to run SSL tests: %s\n", err) 350 os.Exit(1) 351 } 352 } 353 354 if *jsonOutput != "" { 355 if err := adb("pull", "-p", "/data/local/tmp/boringssl-tmp/results.json", *jsonOutput); err != nil { 356 fmt.Printf("Failed to extract results.json: %s\n", err) 357 os.Exit(1) 358 } 359 } 360 361 if unitTestExit != 0 { 362 os.Exit(unitTestExit) 363 } 364 365 if sslTestExit != 0 { 366 os.Exit(sslTestExit) 367 } 368} 369