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 15 package main 16 17 import ( 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 31 var ( 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 43 func enableUnitTests() bool { 44 return *suite == "all" || *suite == "unit" 45 } 46 47 func enableSSLTests() bool { 48 return *suite == "all" || *suite == "ssl" 49 } 50 51 func 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 61 func 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 110 func 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. 127 func 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 138 type test []string 139 140 func parseTestConfig(filename string) ([]test, error) { 141 in, err := os.Open(filename) 142 if err != nil { 143 return nil, err 144 } 145 defer in.Close() 146 147 decoder := json.NewDecoder(in) 148 var result []test 149 if err := decoder.Decode(&result); err != nil { 150 return nil, err 151 } 152 return result, nil 153 } 154 155 func copyFile(dst, src string) error { 156 srcFile, err := os.Open(src) 157 if err != nil { 158 return err 159 } 160 defer srcFile.Close() 161 162 srcInfo, err := srcFile.Stat() 163 if err != nil { 164 return err 165 } 166 167 dir := filepath.Dir(dst) 168 if err := os.MkdirAll(dir, 0777); err != nil { 169 return err 170 } 171 172 dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, srcInfo.Mode()) 173 if err != nil { 174 return err 175 } 176 defer dstFile.Close() 177 178 _, err = io.Copy(dstFile, srcFile) 179 return err 180 } 181 182 func main() { 183 flag.Parse() 184 185 if *suite == "all" && *jsonOutput != "" { 186 fmt.Printf("To use -json-output flag, select only one test suite with -suite.\n") 187 os.Exit(1) 188 } 189 190 setWorkingDirectory() 191 192 // Clear the target directory. 193 if err := adb("shell", "rm -Rf /data/local/tmp/boringssl-tmp"); err != nil { 194 fmt.Printf("Failed to clear target directory: %s\n", err) 195 os.Exit(1) 196 } 197 198 // Stage everything in a temporary directory. 199 tmpDir, err := ioutil.TempDir("", "boringssl-android") 200 if err != nil { 201 fmt.Printf("Error making temporary directory: %s\n", err) 202 os.Exit(1) 203 } 204 defer os.RemoveAll(tmpDir) 205 206 var binaries, files []string 207 208 if enableUnitTests() { 209 files = append(files, 210 "util/all_tests.json", 211 "BUILDING.md", 212 ) 213 214 tests, err := parseTestConfig("util/all_tests.json") 215 if err != nil { 216 fmt.Printf("Failed to parse input: %s\n", err) 217 os.Exit(1) 218 } 219 220 seenBinary := make(map[string]struct{}) 221 for _, test := range tests { 222 if _, ok := seenBinary[test[0]]; !ok { 223 binaries = append(binaries, test[0]) 224 seenBinary[test[0]] = struct{}{} 225 } 226 for _, arg := range test[1:] { 227 if strings.Contains(arg, "/") { 228 files = append(files, arg) 229 } 230 } 231 } 232 233 fmt.Printf("Building all_tests...\n") 234 if err := goTool("build", "-o", filepath.Join(tmpDir, "util/all_tests"), "util/all_tests.go"); err != nil { 235 fmt.Printf("Error building all_tests.go: %s\n", err) 236 os.Exit(1) 237 } 238 } 239 240 if enableSSLTests() { 241 binaries = append(binaries, "ssl/test/bssl_shim") 242 files = append(files, 243 "BUILDING.md", 244 "ssl/test/runner/cert.pem", 245 "ssl/test/runner/channel_id_key.pem", 246 "ssl/test/runner/ecdsa_p224_cert.pem", 247 "ssl/test/runner/ecdsa_p224_key.pem", 248 "ssl/test/runner/ecdsa_p256_cert.pem", 249 "ssl/test/runner/ecdsa_p256_key.pem", 250 "ssl/test/runner/ecdsa_p384_cert.pem", 251 "ssl/test/runner/ecdsa_p384_key.pem", 252 "ssl/test/runner/ecdsa_p521_cert.pem", 253 "ssl/test/runner/ecdsa_p521_key.pem", 254 "ssl/test/runner/ed25519_cert.pem", 255 "ssl/test/runner/ed25519_key.pem", 256 "ssl/test/runner/key.pem", 257 "ssl/test/runner/rsa_1024_cert.pem", 258 "ssl/test/runner/rsa_1024_key.pem", 259 "ssl/test/runner/rsa_chain_cert.pem", 260 "ssl/test/runner/rsa_chain_key.pem", 261 "util/all_tests.json", 262 ) 263 264 fmt.Printf("Building runner...\n") 265 if err := goTool("test", "-c", "-o", filepath.Join(tmpDir, "ssl/test/runner/runner"), "./ssl/test/runner/"); err != nil { 266 fmt.Printf("Error building runner: %s\n", err) 267 os.Exit(1) 268 } 269 } 270 271 fmt.Printf("Copying test binaries...\n") 272 for _, binary := range binaries { 273 if err := copyFile(filepath.Join(tmpDir, "build", binary), filepath.Join(*buildDir, binary)); err != nil { 274 fmt.Printf("Failed to copy %s: %s\n", binary, err) 275 os.Exit(1) 276 } 277 } 278 279 fmt.Printf("Copying data files...\n") 280 for _, file := range files { 281 if err := copyFile(filepath.Join(tmpDir, file), file); err != nil { 282 fmt.Printf("Failed to copy %s: %s\n", file, err) 283 os.Exit(1) 284 } 285 } 286 287 fmt.Printf("Uploading files...\n") 288 if err := adb("push", "-p", tmpDir, "/data/local/tmp/boringssl-tmp"); err != nil { 289 fmt.Printf("Failed to push runner: %s\n", err) 290 os.Exit(1) 291 } 292 293 var unitTestExit int 294 if enableUnitTests() { 295 fmt.Printf("Running unit tests...\n") 296 unitTestExit, err = adbShell(fmt.Sprintf("cd /data/local/tmp/boringssl-tmp && ./util/all_tests -json-output results.json %s", *allTestsArgs)) 297 if err != nil { 298 fmt.Printf("Failed to run unit tests: %s\n", err) 299 os.Exit(1) 300 } 301 } 302 303 var sslTestExit int 304 if enableSSLTests() { 305 fmt.Printf("Running SSL tests...\n") 306 sslTestExit, err = adbShell(fmt.Sprintf("cd /data/local/tmp/boringssl-tmp/ssl/test/runner && ./runner -json-output ../../../results.json %s", *runnerArgs)) 307 if err != nil { 308 fmt.Printf("Failed to run SSL tests: %s\n", err) 309 os.Exit(1) 310 } 311 } 312 313 if *jsonOutput != "" { 314 if err := adb("pull", "-p", "/data/local/tmp/boringssl-tmp/results.json", *jsonOutput); err != nil { 315 fmt.Printf("Failed to extract results.json: %s\n", err) 316 os.Exit(1) 317 } 318 } 319 320 if unitTestExit != 0 { 321 os.Exit(unitTestExit) 322 } 323 324 if sslTestExit != 0 { 325 os.Exit(sslTestExit) 326 } 327 } 328