blob: 5eae742dabcce8f235d5b3a6f85c61a0432cf89c [file] [log] [blame]
David Benjaminaf18cdd2016-04-23 01:40:03 -04001// 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
David Benjamin0d1b0962016-08-01 09:50:57 -040013// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
David Benjaminaf18cdd2016-04-23 01:40:03 -040014
David Benjaminf945952d2016-03-07 15:30:26 -050015package main
16
17import (
David Benjamin93512662020-01-12 10:43:04 -050018 "bufio"
David Benjamined9c8fc2016-06-08 09:40:32 -040019 "bytes"
David Benjamin93512662020-01-12 10:43:04 -050020 "errors"
David Benjaminf945952d2016-03-07 15:30:26 -050021 "flag"
22 "fmt"
23 "io"
24 "io/ioutil"
25 "os"
26 "os/exec"
27 "path/filepath"
David Benjamin93512662020-01-12 10:43:04 -050028 "runtime"
David Benjamined9c8fc2016-06-08 09:40:32 -040029 "strconv"
David Benjaminf945952d2016-03-07 15:30:26 -050030 "strings"
David Benjaminaadb4632020-01-16 18:25:37 -050031
32 "boringssl.googlesource.com/boringssl/util/testconfig"
David Benjaminf945952d2016-03-07 15:30:26 -050033)
34
35var (
36 buildDir = flag.String("build-dir", "build", "Specifies the build directory to push.")
David Benjamin00b10692016-05-19 00:13:22 -040037 adbPath = flag.String("adb", "adb", "Specifies the adb binary to use. Defaults to looking in PATH.")
David Benjamin93512662020-01-12 10:43:04 -050038 ndkPath = flag.String("ndk", "", "Specifies the path to the NDK installation. Defaults to detecting from the build directory.")
David Benjaminf945952d2016-03-07 15:30:26 -050039 device = flag.String("device", "", "Specifies the device or emulator. See adb's -s argument.")
David Benjamin93512662020-01-12 10:43:04 -050040 abi = flag.String("abi", "", "Specifies the Android ABI to use when building Go tools. Defaults to detecting from the build directory.")
41 apiLevel = flag.Int("api-level", 0, "Specifies the Android API level to use when building Go tools. Defaults to detecting from the build directory.")
David Benjamin8de8b3d2016-05-12 23:07:47 -040042 suite = flag.String("suite", "all", "Specifies the test suites to run (all, unit, or ssl).")
David Benjaminf945952d2016-03-07 15:30:26 -050043 allTestsArgs = flag.String("all-tests-args", "", "Specifies space-separated arguments to pass to all_tests.go")
44 runnerArgs = flag.String("runner-args", "", "Specifies space-separated arguments to pass to ssl/test/runner")
David Benjamin8de8b3d2016-05-12 23:07:47 -040045 jsonOutput = flag.String("json-output", "", "The file to output JSON results to.")
David Benjaminf945952d2016-03-07 15:30:26 -050046)
47
David Benjamin8de8b3d2016-05-12 23:07:47 -040048func enableUnitTests() bool {
49 return *suite == "all" || *suite == "unit"
50}
51
52func enableSSLTests() bool {
53 return *suite == "all" || *suite == "ssl"
54}
55
David Benjaminf945952d2016-03-07 15:30:26 -050056func adb(args ...string) error {
57 if len(*device) > 0 {
58 args = append([]string{"-s", *device}, args...)
59 }
David Benjamin00b10692016-05-19 00:13:22 -040060 cmd := exec.Command(*adbPath, args...)
David Benjaminf945952d2016-03-07 15:30:26 -050061 cmd.Stdout = os.Stdout
62 cmd.Stderr = os.Stderr
63 return cmd.Run()
64}
65
David Benjamined9c8fc2016-06-08 09:40:32 -040066func adbShell(shellCmd string) (int, error) {
67 var args []string
68 if len(*device) > 0 {
69 args = append([]string{"-s", *device}, args...)
70 }
71 args = append(args, "shell")
72
73 const delimiter = "___EXIT_CODE___"
74
75 // Older versions of adb and Android do not preserve the exit
76 // code, so work around this.
77 // https://code.google.com/p/android/issues/detail?id=3254
78 shellCmd += "; echo " + delimiter + " $?"
79 args = append(args, shellCmd)
80
81 cmd := exec.Command(*adbPath, args...)
82 stdout, err := cmd.StdoutPipe()
83 if err != nil {
84 return 0, err
85 }
86 cmd.Stderr = os.Stderr
87 if err := cmd.Start(); err != nil {
88 return 0, err
89 }
90
91 var stdoutBytes bytes.Buffer
92 for {
93 var buf [1024]byte
94 n, err := stdout.Read(buf[:])
95 stdoutBytes.Write(buf[:n])
96 os.Stdout.Write(buf[:n])
97 if err != nil {
98 break
99 }
100 }
101
102 if err := cmd.Wait(); err != nil {
103 return 0, err
104 }
105
106 stdoutStr := stdoutBytes.String()
107 idx := strings.LastIndex(stdoutStr, delimiter)
108 if idx < 0 {
109 return 0, fmt.Errorf("Could not find delimiter in output.")
110 }
111
112 return strconv.Atoi(strings.TrimSpace(stdoutStr[idx+len(delimiter):]))
113}
114
David Benjaminf945952d2016-03-07 15:30:26 -0500115func goTool(args ...string) error {
116 cmd := exec.Command("go", args...)
117 cmd.Stdout = os.Stdout
118 cmd.Stderr = os.Stderr
119
David Benjamin1147be02016-05-19 13:23:11 -0400120 cmd.Env = os.Environ()
David Benjamin93512662020-01-12 10:43:04 -0500121
122 // The NDK includes the host platform in the toolchain path.
123 var ndkOS, ndkArch string
124 switch runtime.GOOS {
125 case "linux":
126 ndkOS = "linux"
127 default:
128 return fmt.Errorf("unknown host OS: %q", runtime.GOOS)
David Benjaminf945952d2016-03-07 15:30:26 -0500129 }
David Benjamin93512662020-01-12 10:43:04 -0500130 switch runtime.GOARCH {
131 case "amd64":
132 ndkArch = "x86_64"
133 default:
134 return fmt.Errorf("unknown host architecture: %q", runtime.GOARCH)
135 }
136 ndkHost := ndkOS + "-" + ndkArch
137
138 // Use the NDK's target-prefixed clang wrappers, so cgo gets the right
139 // flags. See https://developer.android.com/ndk/guides/cmake#android_abi for
140 // Android ABIs.
141 var targetPrefix string
142 switch *abi {
143 case "armeabi-v7a", "armeabi-v7a with NEON":
144 targetPrefix = fmt.Sprintf("armv7a-linux-androideabi%d-", *apiLevel)
145 cmd.Env = append(cmd.Env, "GOARCH=arm")
146 cmd.Env = append(cmd.Env, "GOARM=7")
147 case "arm64-v8a":
148 targetPrefix = fmt.Sprintf("aarch64-linux-android%d-", *apiLevel)
149 cmd.Env = append(cmd.Env, "GOARCH=arm64")
150 default:
151 fmt.Errorf("unknown Android ABI: %q", *abi)
152 }
153
154 // Go's Android support requires cgo and compilers from the NDK. See
155 // https://golang.org/misc/android/README, though note CC_FOR_TARGET only
156 // works when building Go itself. go build only looks at CC.
157 cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
158 cmd.Env = append(cmd.Env, "GOOS=android")
159 toolchainDir := filepath.Join(*ndkPath, "toolchains", "llvm", "prebuilt", ndkHost, "bin")
160 cmd.Env = append(cmd.Env, fmt.Sprintf("CC=%s", filepath.Join(toolchainDir, targetPrefix+"clang")))
161 cmd.Env = append(cmd.Env, fmt.Sprintf("CXX=%s", filepath.Join(toolchainDir, targetPrefix+"clang++")))
David Benjaminf945952d2016-03-07 15:30:26 -0500162 return cmd.Run()
163}
164
165// setWorkingDirectory walks up directories as needed until the current working
166// directory is the top of a BoringSSL checkout.
167func setWorkingDirectory() {
168 for i := 0; i < 64; i++ {
169 if _, err := os.Stat("BUILDING.md"); err == nil {
170 return
171 }
172 os.Chdir("..")
173 }
174
175 panic("Couldn't find BUILDING.md in a parent directory!")
176}
177
David Benjamin93512662020-01-12 10:43:04 -0500178func detectOptionsFromCMake() error {
179 if len(*ndkPath) != 0 && len(*abi) != 0 && *apiLevel != 0 {
180 // No need to parse options from CMake.
181 return nil
182 }
183
184 cmakeCache, err := os.Open(filepath.Join(*buildDir, "CMakeCache.txt"))
185 if err != nil {
186 return err
187 }
188 defer cmakeCache.Close()
189
190 cmakeVars := make(map[string]string)
191 scanner := bufio.NewScanner(cmakeCache)
192 for scanner.Scan() {
193 line := scanner.Text()
194 if idx := strings.IndexByte(line, '#'); idx >= 0 {
195 line = line[:idx]
196 }
197 if idx := strings.Index(line, "//"); idx >= 0 {
198 line = line[:idx]
199 }
200 // The syntax for each line is KEY:TYPE=VALUE.
201 equals := strings.IndexByte(line, '=')
202 if equals < 0 {
203 continue
204 }
205 name := line[:equals]
206 value := line[equals+1:]
207 if idx := strings.IndexByte(name, ':'); idx >= 0 {
208 name = name[:idx]
209 }
210 cmakeVars[name] = value
211 }
212 if err := scanner.Err(); err != nil {
213 return err
214 }
215
216 if len(*ndkPath) == 0 {
David Benjaminf50a8a72020-01-13 18:14:07 -0500217 if ndk, ok := cmakeVars["ANDROID_NDK"]; ok {
218 *ndkPath = ndk
219 } else if toolchainFile, ok := cmakeVars["CMAKE_TOOLCHAIN_FILE"]; ok {
220 // The toolchain is at build/cmake/android.toolchain.cmake under the NDK.
221 *ndkPath = filepath.Dir(filepath.Dir(filepath.Dir(toolchainFile)))
222 } else {
223 return errors.New("Neither CMAKE_TOOLCHAIN_FILE nor ANDROID_NDK found in CMakeCache.txt")
David Benjamin93512662020-01-12 10:43:04 -0500224 }
225 fmt.Printf("Detected NDK path %q from CMakeCache.txt.\n", *ndkPath)
226 }
227 if len(*abi) == 0 {
228 var ok bool
229 if *abi, ok = cmakeVars["ANDROID_ABI"]; !ok {
230 return errors.New("ANDROID_ABI not found in CMakeCache.txt")
231 }
232 fmt.Printf("Detected ABI %q from CMakeCache.txt.\n", *abi)
233 }
234 if *apiLevel == 0 {
235 apiLevelStr, ok := cmakeVars["ANDROID_NATIVE_API_LEVEL"]
236 if !ok {
237 return errors.New("ANDROID_NATIVE_API_LEVEL not found in CMakeCache.txt")
238 }
239 var err error
240 if *apiLevel, err = strconv.Atoi(apiLevelStr); err != nil {
241 return fmt.Errorf("error parsing ANDROID_NATIVE_API_LEVEL: %s", err)
242 }
243 fmt.Printf("Detected API level %d from CMakeCache.txt.\n", *apiLevel)
244 }
245 return nil
246}
247
David Benjaminf945952d2016-03-07 15:30:26 -0500248func copyFile(dst, src string) error {
249 srcFile, err := os.Open(src)
250 if err != nil {
251 return err
252 }
253 defer srcFile.Close()
254
255 srcInfo, err := srcFile.Stat()
256 if err != nil {
257 return err
258 }
259
260 dir := filepath.Dir(dst)
261 if err := os.MkdirAll(dir, 0777); err != nil {
262 return err
263 }
264
265 dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, srcInfo.Mode())
266 if err != nil {
267 return err
268 }
269 defer dstFile.Close()
270
271 _, err = io.Copy(dstFile, srcFile)
272 return err
273}
274
275func main() {
276 flag.Parse()
David Benjaminf945952d2016-03-07 15:30:26 -0500277
David Benjamin8de8b3d2016-05-12 23:07:47 -0400278 if *suite == "all" && *jsonOutput != "" {
279 fmt.Printf("To use -json-output flag, select only one test suite with -suite.\n")
David Benjaminf945952d2016-03-07 15:30:26 -0500280 os.Exit(1)
281 }
282
David Benjamin8de8b3d2016-05-12 23:07:47 -0400283 setWorkingDirectory()
David Benjamin93512662020-01-12 10:43:04 -0500284 if err := detectOptionsFromCMake(); err != nil {
285 fmt.Printf("Error reading options from CMake: %s.\n", err)
286 os.Exit(1)
287 }
David Benjamin8de8b3d2016-05-12 23:07:47 -0400288
David Benjaminf945952d2016-03-07 15:30:26 -0500289 // Clear the target directory.
290 if err := adb("shell", "rm -Rf /data/local/tmp/boringssl-tmp"); err != nil {
291 fmt.Printf("Failed to clear target directory: %s\n", err)
292 os.Exit(1)
293 }
294
295 // Stage everything in a temporary directory.
296 tmpDir, err := ioutil.TempDir("", "boringssl-android")
297 if err != nil {
298 fmt.Printf("Error making temporary directory: %s\n", err)
299 os.Exit(1)
300 }
301 defer os.RemoveAll(tmpDir)
302
David Benjamin8de8b3d2016-05-12 23:07:47 -0400303 var binaries, files []string
304
305 if enableUnitTests() {
306 files = append(files,
307 "util/all_tests.json",
308 "BUILDING.md",
309 )
310
David Benjaminaadb4632020-01-16 18:25:37 -0500311 tests, err := testconfig.ParseTestConfig("util/all_tests.json")
David Benjamin8de8b3d2016-05-12 23:07:47 -0400312 if err != nil {
313 fmt.Printf("Failed to parse input: %s\n", err)
314 os.Exit(1)
David Benjaminf945952d2016-03-07 15:30:26 -0500315 }
David Benjamin8de8b3d2016-05-12 23:07:47 -0400316
317 seenBinary := make(map[string]struct{})
318 for _, test := range tests {
David Benjaminaadb4632020-01-16 18:25:37 -0500319 if _, ok := seenBinary[test.Cmd[0]]; !ok {
320 binaries = append(binaries, test.Cmd[0])
321 seenBinary[test.Cmd[0]] = struct{}{}
David Benjaminf945952d2016-03-07 15:30:26 -0500322 }
David Benjaminaadb4632020-01-16 18:25:37 -0500323 for _, arg := range test.Cmd[1:] {
David Benjamin8de8b3d2016-05-12 23:07:47 -0400324 if strings.Contains(arg, "/") {
325 files = append(files, arg)
326 }
327 }
328 }
329
330 fmt.Printf("Building all_tests...\n")
331 if err := goTool("build", "-o", filepath.Join(tmpDir, "util/all_tests"), "util/all_tests.go"); err != nil {
332 fmt.Printf("Error building all_tests.go: %s\n", err)
333 os.Exit(1)
334 }
335 }
336
337 if enableSSLTests() {
338 binaries = append(binaries, "ssl/test/bssl_shim")
339 files = append(files,
340 "BUILDING.md",
David Benjamin8de8b3d2016-05-12 23:07:47 -0400341 "ssl/test/runner/cert.pem",
342 "ssl/test/runner/channel_id_key.pem",
David Benjamin218f51b2017-02-27 16:41:47 -0500343 "ssl/test/runner/ecdsa_p224_cert.pem",
344 "ssl/test/runner/ecdsa_p224_key.pem",
David Benjamin0c222952016-07-12 15:08:24 -0400345 "ssl/test/runner/ecdsa_p256_cert.pem",
346 "ssl/test/runner/ecdsa_p256_key.pem",
347 "ssl/test/runner/ecdsa_p384_cert.pem",
348 "ssl/test/runner/ecdsa_p384_key.pem",
349 "ssl/test/runner/ecdsa_p521_cert.pem",
350 "ssl/test/runner/ecdsa_p521_key.pem",
David Benjamin0ef8c7b2017-04-06 11:48:59 -0400351 "ssl/test/runner/ed25519_cert.pem",
352 "ssl/test/runner/ed25519_key.pem",
David Benjamin8de8b3d2016-05-12 23:07:47 -0400353 "ssl/test/runner/key.pem",
David Benjamin7944a9f2016-07-12 22:27:01 -0400354 "ssl/test/runner/rsa_1024_cert.pem",
355 "ssl/test/runner/rsa_1024_key.pem",
David Benjamine8b554d2016-11-15 10:43:13 +0900356 "ssl/test/runner/rsa_chain_cert.pem",
357 "ssl/test/runner/rsa_chain_key.pem",
358 "util/all_tests.json",
David Benjamin8de8b3d2016-05-12 23:07:47 -0400359 )
360
361 fmt.Printf("Building runner...\n")
362 if err := goTool("test", "-c", "-o", filepath.Join(tmpDir, "ssl/test/runner/runner"), "./ssl/test/runner/"); err != nil {
363 fmt.Printf("Error building runner: %s\n", err)
364 os.Exit(1)
David Benjaminf945952d2016-03-07 15:30:26 -0500365 }
366 }
367
David Benjamin7458ded2019-10-11 17:05:20 -0400368 var libraries []string
369 if _, err := os.Stat(filepath.Join(*buildDir, "crypto/libcrypto.so")); err == nil {
370 libraries = []string{
371 "libboringssl_gtest.so",
372 "crypto/libcrypto.so",
373 "decrepit/libdecrepit.so",
374 "ssl/libssl.so",
375 }
376 } else if !os.IsNotExist(err) {
377 fmt.Printf("Failed to stat crypto/libcrypto.so: %s\n", err)
378 os.Exit(1)
379 }
380
David Benjaminf945952d2016-03-07 15:30:26 -0500381 fmt.Printf("Copying test binaries...\n")
382 for _, binary := range binaries {
383 if err := copyFile(filepath.Join(tmpDir, "build", binary), filepath.Join(*buildDir, binary)); err != nil {
384 fmt.Printf("Failed to copy %s: %s\n", binary, err)
385 os.Exit(1)
386 }
387 }
388
David Benjamin7458ded2019-10-11 17:05:20 -0400389 var envPrefix string
390 if len(libraries) > 0 {
391 fmt.Printf("Copying libraries...\n")
392 for _, library := range libraries {
393 // Place all the libraries in a common directory so they
394 // can be passed to LD_LIBRARY_PATH once.
395 if err := copyFile(filepath.Join(tmpDir, "build", "lib", filepath.Base(library)), filepath.Join(*buildDir, library)); err != nil {
396 fmt.Printf("Failed to copy %s: %s\n", library, err)
397 os.Exit(1)
398 }
399 }
400 envPrefix = "env LD_LIBRARY_PATH=/data/local/tmp/boringssl-tmp/build/lib "
401 }
402
David Benjaminf945952d2016-03-07 15:30:26 -0500403 fmt.Printf("Copying data files...\n")
404 for _, file := range files {
405 if err := copyFile(filepath.Join(tmpDir, file), file); err != nil {
406 fmt.Printf("Failed to copy %s: %s\n", file, err)
407 os.Exit(1)
408 }
409 }
410
David Benjaminf945952d2016-03-07 15:30:26 -0500411 fmt.Printf("Uploading files...\n")
412 if err := adb("push", "-p", tmpDir, "/data/local/tmp/boringssl-tmp"); err != nil {
413 fmt.Printf("Failed to push runner: %s\n", err)
414 os.Exit(1)
415 }
416
David Benjamined9c8fc2016-06-08 09:40:32 -0400417 var unitTestExit int
David Benjamin8de8b3d2016-05-12 23:07:47 -0400418 if enableUnitTests() {
419 fmt.Printf("Running unit tests...\n")
David Benjamin7458ded2019-10-11 17:05:20 -0400420 unitTestExit, err = adbShell(fmt.Sprintf("cd /data/local/tmp/boringssl-tmp && %s./util/all_tests -json-output results.json %s", envPrefix, *allTestsArgs))
David Benjamined9c8fc2016-06-08 09:40:32 -0400421 if err != nil {
David Benjamin8de8b3d2016-05-12 23:07:47 -0400422 fmt.Printf("Failed to run unit tests: %s\n", err)
423 os.Exit(1)
424 }
David Benjaminf945952d2016-03-07 15:30:26 -0500425 }
426
David Benjamined9c8fc2016-06-08 09:40:32 -0400427 var sslTestExit int
David Benjamin8de8b3d2016-05-12 23:07:47 -0400428 if enableSSLTests() {
429 fmt.Printf("Running SSL tests...\n")
David Benjamin7458ded2019-10-11 17:05:20 -0400430 sslTestExit, err = adbShell(fmt.Sprintf("cd /data/local/tmp/boringssl-tmp/ssl/test/runner && %s./runner -json-output ../../../results.json %s", envPrefix, *runnerArgs))
David Benjamined9c8fc2016-06-08 09:40:32 -0400431 if err != nil {
David Benjamin8de8b3d2016-05-12 23:07:47 -0400432 fmt.Printf("Failed to run SSL tests: %s\n", err)
433 os.Exit(1)
434 }
435 }
436
437 if *jsonOutput != "" {
438 if err := adb("pull", "-p", "/data/local/tmp/boringssl-tmp/results.json", *jsonOutput); err != nil {
439 fmt.Printf("Failed to extract results.json: %s\n", err)
440 os.Exit(1)
441 }
David Benjaminf945952d2016-03-07 15:30:26 -0500442 }
David Benjamined9c8fc2016-06-08 09:40:32 -0400443
444 if unitTestExit != 0 {
445 os.Exit(unitTestExit)
446 }
447
448 if sslTestExit != 0 {
449 os.Exit(sslTestExit)
450 }
David Benjaminf945952d2016-03-07 15:30:26 -0500451}