1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.result;
18 
19 import com.android.ddmlib.Log.LogLevel;
20 import com.android.tradefed.config.Option;
21 import com.android.tradefed.config.OptionClass;
22 import com.android.tradefed.log.LogUtil.CLog;
23 
24 import com.google.common.annotations.VisibleForTesting;
25 
26 import java.io.IOException;
27 import java.io.UncheckedIOException;
28 import java.nio.file.FileSystem;
29 import java.nio.file.FileSystems;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 
33 /**
34  * A custom Tradefed reporter for Bazel test rules.
35  *
36  * <p>This custom result reporter computes and exports the exit code for Bazel to determine whether
37  * a test target passes or fails. The file is written to a file for downstream test rules to read
38  * and is required because Tradefed commands terminate with a 0 exit code despite test failures.
39  */
40 @OptionClass(alias = "bazel-exit-code-result-reporter")
41 public final class BazelExitCodeResultReporter implements ITestInvocationListener {
42 
43     private final FileSystem mFileSystem;
44 
45     // This is not a File object in order to use an in-memory FileSystem in tests. Using Path would
46     // have been more appropriate but Tradefed does not support option fields of that type.
47     @Option(name = "file", mandatory = true, description = "Bazel exit code file")
48     private String mExitCodeFile;
49 
50     private boolean mHasRunFailures;
51     private boolean mHasTestFailures;
52 
53     @VisibleForTesting
BazelExitCodeResultReporter(FileSystem fs)54     BazelExitCodeResultReporter(FileSystem fs) {
55         this.mFileSystem = fs;
56     }
57 
BazelExitCodeResultReporter()58     public BazelExitCodeResultReporter() {
59         this(FileSystems.getDefault());
60     }
61 
62     @Override
testRunFailed(String errorMessage)63     public void testRunFailed(String errorMessage) {
64         mHasRunFailures = true;
65     }
66 
67     @Override
testRunFailed(FailureDescription failure)68     public void testRunFailed(FailureDescription failure) {
69         mHasRunFailures = true;
70     }
71 
72     @Override
testFailed(TestDescription test, String trace)73     public void testFailed(TestDescription test, String trace) {
74         mHasTestFailures = true;
75     }
76 
77     @Override
testFailed(TestDescription test, FailureDescription failure)78     public void testFailed(TestDescription test, FailureDescription failure) {
79         mHasTestFailures = true;
80     }
81 
82     @Override
invocationEnded(long elapsedTime)83     public void invocationEnded(long elapsedTime) {
84         writeExitCodeFile();
85     }
86 
writeExitCodeFile()87     private void writeExitCodeFile() {
88         ExitCode code = computeExitCode();
89 
90         CLog.logAndDisplay(
91                 LogLevel.INFO,
92                 "Test exit code file generated at %s. Exit Code %s",
93                 mExitCodeFile,
94                 code);
95 
96         try {
97             Path path = mFileSystem.getPath(mExitCodeFile);
98             Files.createDirectories(path.getParent());
99             Files.write(path, String.valueOf(code.value).getBytes());
100         } catch (IOException e) {
101             throw new UncheckedIOException("Failed to write exit code file.", e);
102         }
103     }
104 
computeExitCode()105     private ExitCode computeExitCode() {
106         if (mHasRunFailures) {
107             return ExitCode.RUN_FAILURE;
108         }
109 
110         if (mHasTestFailures) {
111             return ExitCode.TESTS_FAILED;
112         }
113 
114         return ExitCode.SUCCESS;
115     }
116 
117     private enum ExitCode {
118         SUCCESS(0),
119         TESTS_FAILED(3),
120         RUN_FAILURE(6);
121 
122         private final int value;
123 
ExitCode(int value)124         ExitCode(int value) {
125             this.value = value;
126         }
127     }
128 }
129