blob: edb0b6ccc0b4de85a11823e4a4e4116db2be6e57 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.testtype;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.Log;
import com.android.ddmlib.MultiLineReceiver;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.tradefed.result.ITestLifeCycleReceiver;
import com.android.tradefed.result.TestDescription;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* Parses the 'raw output mode' results of VTS fuzz tests that run from shell, and informs
* a ITestRunListener of the results.
* <p>Sample format of output expected:
*
* <pre>
* ...
* [ PASSED ]
* </pre>
*
* If a fuzz test run finds a problem, the process crashes or hangs and thus [ PASSED ] is not
* printed. All other lines are ignored.
*/
public class VtsFuzzTestResultParser extends MultiLineReceiver {
private static final String LOG_TAG = "GTestResultParser";
// Variables to keep track of state
private int mNumTestsRun = 0;
private int mNumTestsExpected = 1;
private int mTotalNumberOfTestFailed = 0;
private final String mTestRunName;
private final Collection<ITestLifeCycleReceiver> mTestListeners;
private TestDescription mTestId;
/** True if start of test has already been reported to listener. */
private boolean mTestRunStartReported = false;
/** Stack data structure to keep the logs. */
private StringBuilder mStackTrace = null;
/** True if current test run has been canceled by user. */
private boolean mIsCancelled = false;
/** Prefixes used to demarcate and identify output. */
private static class Prefixes {
@SuppressWarnings("unused")
private static final String PASSED_MARKER = "[ PASSED ]";
private static final String FAILED_MARKER = "[ FAILED ]";
private static final String TIMEOUT_MARKER = "[ TIMEOUT ]";
}
/**
* Creates the GTestResultParser.
*
* @param testRunName the test run name to provide to
* {@link ITestRunListener#testRunStarted(String, int)}
* @param listeners informed of test results as the tests are executing
*/
public VtsFuzzTestResultParser(
String testRunName, Collection<ITestLifeCycleReceiver> listeners) {
mTestRunName = testRunName;
mTestListeners = new ArrayList<>(listeners);
mStackTrace = new StringBuilder();
}
/**
* Creates the GTestResultParser for a single listener.
*
* @param testRunName the test run name to provide to
* {@link ITestRunListener#testRunStarted(String, int)}
* @param listener informed of test results as the tests are executing
*/
public VtsFuzzTestResultParser(String testRunName, ITestLifeCycleReceiver listener) {
mTestRunName = testRunName;
mTestListeners = new ArrayList<>(1);
mTestListeners.add(listener);
mStackTrace = new StringBuilder();
}
/**
* {@inheritDoc}
*/
@Override
public void processNewLines(String[] lines) {
if (!mTestRunStartReported) {
// current test results are cleared out after every complete test run,
// if it's not null, assume the last test caused this and report as a test failure
mTestId = new TestDescription("fuzzer", mTestRunName);
for (ITestLifeCycleReceiver listener : mTestListeners) {
listener.testRunStarted(mTestRunName, mNumTestsExpected);
}
mTestRunStartReported = true;
}
for (String line : lines) {
if (line.contains(Prefixes.PASSED_MARKER)) {
doTestEnded(true);
} else if (line.contains(Prefixes.FAILED_MARKER)
|| line.contains(Prefixes.TIMEOUT_MARKER)) {
doTestEnded(false);
} else {
mStackTrace.append(line + "\r\n");
}
Log.v(LOG_TAG, line);
}
}
/**
* Helper method to do the work necessary when a test has ended.
*
* @param testPassed Indicates whether the test passed or failed (set to true if passed, false
* if failed)
*/
private void doTestEnded(boolean testPassed) {
if (!testPassed) { // test failed
for (ITestLifeCycleReceiver listener : mTestListeners) {
listener.testFailed(mTestId, mStackTrace.toString());
}
++mTotalNumberOfTestFailed;
}
// For all cases (pass or fail), we ultimately need to report test has ended
for (ITestLifeCycleReceiver listener : mTestListeners) {
Map <String, String> emptyMap = Collections.emptyMap();
listener.testEnded(mTestId, emptyMap);
}
++mNumTestsRun;
}
/**
* Process an instrumentation run failure
*
* @param errorMsg The message to output about the nature of the error
*/
private void handleTestRunFailed(String errorMsg) {
errorMsg = (errorMsg == null ? "Unknown error" : errorMsg);
Log.i(LOG_TAG, String.format("Test run failed: %s", errorMsg));
// If there was any stack trace during the test run, append it to the "test failed"
// error message so we have an idea of what caused the crash/failure.
Map<String, String> emptyMap = Collections.emptyMap();
for (ITestLifeCycleReceiver listener : mTestListeners) {
listener.testFailed(mTestId, mStackTrace.toString());
listener.testEnded(mTestId, emptyMap);
}
// Report the test run failed
for (ITestLifeCycleReceiver listener : mTestListeners) {
listener.testRunFailed(errorMsg);
}
}
/**
* Called by parent when adb session is complete.
*/
@Override
public void done() {
super.done();
if (mNumTestsRun < mNumTestsExpected) {
handleTestRunFailed(String.format("Test run failed. Expected %d tests, received %d",
mNumTestsExpected, mNumTestsRun));
}
}
/**
* Returns true if test run canceled.
*
* @see IShellOutputReceiver#isCancelled()
*/
@Override
public boolean isCancelled() {
return mIsCancelled;
}
/**
* Requests cancellation of test run.
*/
public void cancel() {
mIsCancelled = true;
}
}