From 97ddd4608cef90bf2c76d3324680f89f7fe4a93c Mon Sep 17 00:00:00 2001 From: Hakan Lindh Date: Thu, 18 May 2017 12:33:17 -0700 Subject: Adding test for 'adb screenrecord' Adding test to verify that "adb screenrecord" command works. Test has optional parameters: --time-limit (seconds) --bit-rate (megabits per second) --size (e.g. 1280x720) Bug: 34596564 Fixes: Test: run google/test/framework/media/adb-screen-record [options] -s Change-Id: I04b4c1406a408066c87a5691cd8c25195d740666 --- .../android/media/tests/AdbScreenrecordTest.java | 390 +++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 src/com/android/media/tests/AdbScreenrecordTest.java (limited to 'src') diff --git a/src/com/android/media/tests/AdbScreenrecordTest.java b/src/com/android/media/tests/AdbScreenrecordTest.java new file mode 100644 index 0000000..6db0ef2 --- /dev/null +++ b/src/com/android/media/tests/AdbScreenrecordTest.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2017 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.media.tests; + +import com.android.ddmlib.CollectingOutputReceiver; +import com.android.ddmlib.testrunner.TestIdentifier; +import com.android.tradefed.config.Option; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.IFileEntry; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.result.ITestInvocationListener; +import com.android.tradefed.testtype.IDeviceTest; +import com.android.tradefed.testtype.IRemoteTest; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; +import com.android.tradefed.util.FileUtil; +import com.android.tradefed.util.RunUtil; + +import java.io.File; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Tests adb command "screenrecord", i.e. "adb screenrecord [--size] [--bit-rate] [--time-limit]" + * + *

The test use the above command to record a video of DUT's screen. It then tries to verify that + * a video was actually recorded and that the video is a valid video file. It currently uses + * 'avprobe' to do the video analysis along with extracting parameters from the adb command's + * output. + */ +public class AdbScreenrecordTest implements IDeviceTest, IRemoteTest { + + //=================================================================== + // TEST OPTIONS + //=================================================================== + @Option(name = "run-key", description = "Run key for the test") + private String mRunKey = "AdbScreenRecord"; + + @Option(name = "time-limit", description = "Recording time in seconds", isTimeVal = true) + private long mRecordTimeInSeconds = -1; + + @Option(name = "size", description = "Video Size: 'widthxheight', e.g. '1280x720'") + private String mVideoSize = null; + + @Option(name = "bit-rate", description = "Video bit rate in megabits per second, e.g. 4000000") + private long mBitRate = -1; + + //=================================================================== + // CLASS VARIABLES + //=================================================================== + private ITestDevice mDevice; + private TestIdentifier mTestId; + + //=================================================================== + // CONSTANTS + //=================================================================== + private static final long TEST_TIMEOUT_MS = 5 * 60 * 1000; // 5 min + private static final long DEVICE_SYNC_MS = 5 * 60 * 1000; // 5 min + private static final long POLLING_INTERVAL_MS = 5 * 1000; // 5 sec + private static final long CMD_TIMEOUT_MS = 5 * 1000; // 5 sec + private static final String ERR_OPTION_MALFORMED = "Test option %1$s is not correct [%2$s]"; + private static final String OPTION_TIME_LIMIT = "--time-limit"; + private static final String OPTION_SIZE = "--size"; + private static final String OPTION_BITRATE = "--bit-rate"; + private static final String RESULT_KEY_RECORDED_FRAMES = "recorded_frames"; + private static final String RESULT_KEY_RECORDED_LENGTH = "recorded_length"; + private static final String RESULT_KEY_VERIFIED_DURATION = "verified_duration"; + private static final String RESULT_KEY_VERIFIED_BITRATE = "verified_bitrate"; + private static final String TEST_FILE = "/sdcard/screenrecord_test.mp4"; + private static final String AVPROBE_NOT_INSTALLED = + "Program 'avprobe' is not installed on host '%1$s'"; + private static final String REGEX_IS_VIDEO_OK = + "Duration: (\\d\\d:\\d\\d:\\d\\d.\\d\\d).+bitrate: (\\d+ .b\\/s)"; + private static final String AVPROBE_STR = "avprobe"; + + //=================================================================== + // ENUMS + //=================================================================== + enum HOST_SOFTWARE { + AVPROBE + } + + @Override + public void setDevice(ITestDevice device) { + mDevice = device; + } + + @Override + public ITestDevice getDevice() { + return mDevice; + } + + /** Main test function invoked by test harness */ + @Override + public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { + initializeTest(listener); + + CLog.i("Verify that test options are valid"); + if (!verifyTestParameters(listener, mTestId)) { + return; + } + + CLog.i("Verify required software is installed on host"); + verifyRequiredSoftwareIsInstalled(HOST_SOFTWARE.AVPROBE); + + final long testStartTime = System.currentTimeMillis(); + + final Map resultsDictionary = runTest(listener, TEST_TIMEOUT_MS); + + final long testStopTime = System.currentTimeMillis(); + + // "resultDictionary" can be used to post results to dashboards like BlackBox + listener.testEnded(mTestId, resultsDictionary); + listener.testRunEnded(testStopTime - testStartTime, resultsDictionary); + } + + /** + * Test code that calls "adb screenrecord" and checks for pass/fail criterias + * + *

+ * + *

+ */ + private Map runTest(final ITestInvocationListener listener, final long timeout) + throws DeviceNotAvailableException { + final Map results = new HashMap(); + final CollectingOutputReceiver receiver = new CollectingOutputReceiver(); + final String cmd = generateAdbScreenRecordCommand(); + final String deviceFileName = getAbsoluteFilename(); + + CLog.i("START Execute device shell command: '" + cmd + "'"); + getDevice().executeShellCommand(cmd, receiver, timeout, TimeUnit.MILLISECONDS, 3); + String adbOutput = receiver.getOutput(); + CLog.i(adbOutput); + CLog.i("END Execute device shell command"); + + CLog.i("Wait for recorded file: " + deviceFileName); + if (!waitForFile(getDevice(), timeout, deviceFileName)) { + reportFailure(listener, mTestId, "Recorded test file not found"); + // Since we don't have a file, no need to delete it; we can return here + return results; + } + + CLog.i("Get number of recorded frames and recorded length from adb output"); + if (!extractVideoDataFromAdbOutput(listener, adbOutput, results)) { + deleteFileFromDevice(deviceFileName); + return results; + } + + CLog.i("Get duration and bitrate info from video file using '" + AVPROBE_STR + "'"); + extractDurationAndBitrateFromVideoFileUsingAvprobe(listener, deviceFileName, results); + deleteFileFromDevice(deviceFileName); + return results; + } + + /** + * Deletes a file off a device + * + * @param deviceFileName - path and filename to file to be deleted + * @throws DeviceNotAvailableException + */ + private void deleteFileFromDevice(String deviceFileName) throws DeviceNotAvailableException { + CLog.i("Delete file from device: " + deviceFileName); + getDevice().executeShellCommand("rm -f " + deviceFileName); + } + + /** + * Extracts duration and bitrate data from a video file + * + * @throws DeviceNotAvailableException + */ + private boolean extractDurationAndBitrateFromVideoFileUsingAvprobe( + ITestInvocationListener listener, String deviceFileName, Map results) + throws DeviceNotAvailableException { + CLog.i("Check if the recorded file has some data in it: " + deviceFileName); + IFileEntry video = getDevice().getFileEntry(deviceFileName); + if (video == null || video.getFileEntry().getSizeValue() < 1) { + reportFailure(listener, mTestId, "Video Entry info failed"); + return false; + } + + final File recordedVideo = getDevice().pullFile(deviceFileName); + CLog.i("Recorded video file: " + recordedVideo.getAbsolutePath()); + + CommandResult result = + RunUtil.getDefault() + .runTimedCmd( + CMD_TIMEOUT_MS, + AVPROBE_STR, + "-loglevel", + "info", + recordedVideo.getAbsolutePath()); + + // Remove file from host machine + FileUtil.deleteFile(recordedVideo); + + if (result.getStatus() != CommandStatus.SUCCESS) { + reportFailure(listener, mTestId, AVPROBE_STR + " command failed"); + return false; + } + + String data = result.getStderr(); + CLog.i("data: " + data); + if (data == null || data.isEmpty()) { + reportFailure(listener, mTestId, AVPROBE_STR + " output data is empty"); + return false; + } + + Matcher m = Pattern.compile(REGEX_IS_VIDEO_OK).matcher(data); + if (!m.find()) { + final String errMsg = + "Video verification failed; no matching verification pattern found"; + reportFailure(listener, mTestId, errMsg); + return false; + } + + results.put(RESULT_KEY_VERIFIED_DURATION, m.group(1)); + results.put(RESULT_KEY_VERIFIED_BITRATE, m.group(2)); + return true; + } + + /** Extracts recorded number of frames and recorded video length from adb output */ + private boolean extractVideoDataFromAdbOutput( + ITestInvocationListener listener, String adbOutput, Map results) { + final String regEx = "recorded (\\d+) frames in (\\d+) second"; + Matcher m = Pattern.compile(regEx).matcher(adbOutput); + if (!m.find()) { + reportFailure(listener, mTestId, "Regular Expression did not find recorded frames"); + return false; + } + + int recordedFrames = Integer.parseInt(m.group(1)); + int recordedLength = Integer.parseInt(m.group(2)); + CLog.i("Recorded frames: " + recordedFrames); + CLog.i("Recorded length: " + recordedLength); + if (recordedFrames <= 0) { + reportFailure(listener, mTestId, "No recorded frames detected"); + return false; + } + + results.put(RESULT_KEY_RECORDED_FRAMES, Integer.toString(recordedFrames)); + results.put(RESULT_KEY_RECORDED_LENGTH, Integer.toString(recordedLength)); + return true; + } + + /** Generates an adb command from passed in test options */ + private String generateAdbScreenRecordCommand() { + final String SPACE = " "; + StringBuilder sb = new StringBuilder(128); + sb.append("screenrecord --verbose ").append(getAbsoluteFilename()); + + // Add test options if they have been passed in to the test + if (mRecordTimeInSeconds != -1) { + final long timeLimit = TimeUnit.MILLISECONDS.toSeconds(mRecordTimeInSeconds); + sb.append(SPACE).append(OPTION_TIME_LIMIT).append(SPACE).append(timeLimit); + } + + if (mVideoSize != null) { + sb.append(SPACE).append(OPTION_SIZE).append(SPACE).append(mVideoSize); + } + + if (mBitRate != -1) { + sb.append(SPACE).append(OPTION_BITRATE).append(SPACE).append(mBitRate); + } + + return sb.toString(); + } + + /** Returns absolute path to device recorded video file */ + private String getAbsoluteFilename() { + return TEST_FILE; + } + + /** Performs test initialization steps */ + private void initializeTest(ITestInvocationListener listener) + throws UnsupportedOperationException, DeviceNotAvailableException { + mTestId = new TestIdentifier(getClass().getCanonicalName(), mRunKey); + + getDevice().unlockDevice(); + getDevice().waitForDeviceAvailable(DEVICE_SYNC_MS); + + CLog.i("Sync device time to host time"); + getDevice().setDate(new Date()); + + listener.testRunStarted(mRunKey, 1); + listener.testStarted(mTestId); + } + + /** Reports test failure with error message */ + private static void reportFailure( + ITestInvocationListener listener, TestIdentifier testId, String errMsg) { + CLog.e(errMsg); + listener.testFailed(testId, errMsg); + listener.testEnded(testId, new HashMap()); + listener.testRunFailed(errMsg); + } + + /** Verifies that required software is installed on host machine */ + private void verifyRequiredSoftwareIsInstalled(HOST_SOFTWARE software) { + String swName = ""; + switch (software) { + case AVPROBE: + swName = AVPROBE_STR; + CommandResult result = + RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT_MS, swName, "-version"); + String output = result.getStdout(); + if (result.getStatus() == CommandStatus.SUCCESS && output.startsWith(swName)) { + return; + } + break; + } + + CLog.i("Program '" + swName + "' not found, report test failure"); + String hostname = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT_MS, "hostname").getStdout(); + + String err = String.format(AVPROBE_NOT_INSTALLED, (hostname == null) ? "" : hostname); + throw new RuntimeException(err); + } + + /** Verifies that passed in test parameters are legitimate */ + private boolean verifyTestParameters( + ITestInvocationListener listener, final TestIdentifier testId) { + + if (mRecordTimeInSeconds != -1 && mRecordTimeInSeconds < 1) { + final String error = + String.format(ERR_OPTION_MALFORMED, OPTION_TIME_LIMIT, mRecordTimeInSeconds); + reportFailure(listener, testId, error); + return false; + } + + if (mVideoSize != null) { + final String videoSizeRegEx = "\\d+x\\d+"; + Matcher m = Pattern.compile(videoSizeRegEx).matcher(mVideoSize); + if (!m.matches()) { + final String error = String.format(ERR_OPTION_MALFORMED, OPTION_SIZE, mVideoSize); + reportFailure(listener, testId, error); + return false; + } + } + + if (mBitRate != -1 && mBitRate < 1) { + final String error = String.format(ERR_OPTION_MALFORMED, OPTION_BITRATE, mBitRate); + reportFailure(listener, testId, error); + return false; + } + + return true; + } + + /** Checks for existence of a file on the device */ + private static boolean waitForFile( + ITestDevice device, final long timeout, final String absoluteFilename) + throws DeviceNotAvailableException { + final long checkFileStartTime = System.currentTimeMillis(); + + do { + RunUtil.getDefault().sleep(POLLING_INTERVAL_MS); + if (device.doesFileExist(absoluteFilename)) { + return true; + } + } while (System.currentTimeMillis() - checkFileStartTime < timeout); + + return false; + } +} -- cgit v1.2.3