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