| # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import ConfigParser |
| import datetime |
| import os |
| |
| from autotest_lib.client.bin import utils |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.cros.chameleon import chameleon |
| from autotest_lib.client.cros.graphics import graphics_utils |
| from autotest_lib.client.cros.multimedia import display_utility |
| from autotest_lib.client.cros.video import chameleon_screenshot_capturer |
| from autotest_lib.client.cros.video import golden_image_downloader |
| from autotest_lib.client.cros.video import import_screenshot_capturer |
| from autotest_lib.client.cros.video import media_player |
| from autotest_lib.client.cros.video import method_logger |
| from autotest_lib.client.cros.video import screenshot_file_namer |
| from autotest_lib.client.cros.video import sequence_generator |
| from autotest_lib.client.cros.video import video_screenshot_collector |
| |
| |
| class MediaTestFactory(object): |
| """ |
| |
| Responsible for instantiating objects that are needed by other objects. |
| |
| We chose to use this approach in order to separate an object's creation |
| from its use. Most of the classes built in this library demand their |
| dependencies to be supplied in their constructors. |
| Separating object creation from use enables us to build the rest |
| of the system assuming we will be supplied with that we want. |
| The factory takes care of supplying the needed dependencies. |
| |
| It will also isolate to one place that will be changed if we introduce new |
| object that offer a different 'strategy' of doing things. |
| |
| For example new ways to compare images, we will build logic to decide |
| which one here and then supply the client with an object that will do |
| the comparison the 'correct' way. |
| |
| See make_capture_sequence_generator() below for an example. If test asks for |
| a random capture sequence we will create that, or it wants an interval one |
| we will create that. The client of the capture sequence will just *use* the |
| sequence not caring whether it is random or it is with an interval. |
| |
| """ |
| |
| |
| @method_logger.log |
| def __init__(self, tab, http_server, bin_dir, channel, video_name, |
| video_format, video_def): |
| """ |
| Initializes factory. |
| |
| @param tab: object, tab of browser that will load the test page. |
| @param http_server: object, http server to serve the test page |
| @param bin_dir: path to autotest bin directory. This is where autotest |
| will put files that the library code depends on. |
| e.g:Configuration files |
| @param channel: string, The channel of the build this test is running. |
| Configures how granular we want to collect screenshots. |
| See channel_spec.conf. |
| @param video_format: string, format of the video.e.g: mp4 |
| @param video_def: string, definition of video. e.g: 480p |
| |
| Video format and definition will be used to find the path of the video |
| source file stored in the cloud. |
| |
| """ |
| |
| self.tab = tab |
| self.http_server = http_server |
| self.bin_dir = bin_dir |
| |
| self.channel = channel |
| |
| # Configuration file names |
| self.autotest_cros_video_dir = '/usr/local/autotest/cros/video' |
| self.device_spec_filename = 'device_spec.conf' |
| self.test_constants_filename = 'test_constants.conf' |
| self.video_info_filename = 'video_spec.conf' |
| self.channel_spec_filename = 'channel_spec.conf' |
| |
| # HTML file specs |
| self.html_filename = 'video.html' |
| |
| self.device_under_test = None |
| |
| # Video specifications |
| self.video_name = video_name |
| self.video_format = video_format |
| self.video_def = video_def |
| self.time_format = "%H:%M:%S" |
| self.video_source_file = None |
| |
| # Test constants |
| self.test_working_dir = None |
| self.local_golden_images_dir = None |
| self.remote_golden_image_root_dir = None |
| |
| # Screenshot capturing specs |
| self.screenshot_image_format = None |
| self.capture_sequence_style = None |
| self.start_capture = None |
| self.stop_capture = None |
| self.samples_per_min = None |
| self.capture_interval = None |
| |
| # Verification specs |
| self.biopic_project_name = None |
| self.biopic_contact_email = None |
| self.biopic_wait_time = None |
| self.biopic_num_upload_retries = None |
| |
| self.parser = None |
| |
| self._load_configuration() |
| |
| |
| @method_logger.log |
| def _load_configuration(self): |
| """ |
| Loads all configuration parameters from specified configuration files. |
| |
| """ |
| |
| self.parser = ConfigParser.SafeConfigParser() |
| self._load_device_info() |
| self._load_test_constants() |
| self._load_video_info() |
| self._load_channel_specs() |
| |
| |
| @method_logger.log |
| def _load_test_constants(self): |
| """ |
| Reads test constants configuration file and stores parameters. |
| |
| """ |
| self.parser.read(os.path.join(self.autotest_cros_video_dir, |
| self.test_constants_filename)) |
| |
| # test_constants.conf has a constant section storing conf values |
| section = 'constants' |
| |
| self.test_working_dir = self.parser.get(section, 'working_dir') |
| |
| self.local_golden_images_dir = self.parser.get( |
| section, 'local_golden_images_dir') |
| |
| self.screenshot_image_format = self.parser.get(section, 'image_format') |
| |
| self.remote_golden_image_root_dir = self.parser.get( |
| section, 'remote_golden_image_root_dir').replace('\n', '') |
| |
| self.media_id = self.parser.get(section, 'video_id') |
| |
| self.time_out_events_s = self.parser.getint(section, |
| 'time_out_events_s') |
| |
| self.time_btwn_polling_s = self.parser.getfloat(section, |
| 'time_btwn_polling_s') |
| |
| self.capture_sequence_style = self.parser.get(section, |
| 'capture_sequence_style') |
| |
| self.chameleon_interface = self.parser.get(section, |
| 'chameleon_interface') |
| |
| self.timeout_video_input_s = self.parser.getint(section, |
| 'timeout_video_input_s') |
| |
| |
| @method_logger.log |
| def _load_device_info(self): |
| """ |
| Reads device info configuration file and stores parameters. |
| |
| @raises TestError if device resolution was not read. |
| |
| """ |
| |
| res = graphics_utils.get_display_resolution() |
| |
| if res is None: |
| raise error.TestError('Expected a screen resolution. Got None.') |
| |
| self.screen_width_pixels = res[0] |
| self.screen_height_pixels = res[1] |
| |
| dut = utils.get_current_board() |
| |
| self.parser.read(os.path.join(self.autotest_cros_video_dir, |
| self.device_spec_filename)) |
| multires = self.parser.getboolean(dut, 'multires') |
| |
| if multires: |
| dut += '_%d' % self.screen_height_pixels |
| |
| self.top_pixels_to_crop = self.parser.getint(dut, 'top_pixels_to_crop') |
| |
| self.bottom_pixels_to_crop = self.parser.getint(dut, |
| 'bottom_pixels_to_crop') |
| |
| self.device_under_test = dut |
| |
| |
| @method_logger.log |
| def _load_video_info(self): |
| """ |
| Reads video info configuration file and stores parameters. |
| |
| """ |
| self.parser.read(os.path.join(self.autotest_cros_video_dir, |
| self.video_info_filename)) |
| |
| length_str = self.parser.get(self.video_name, 'length') |
| |
| duration = datetime.datetime.strptime(length_str, self.time_format) |
| |
| self.media_length = datetime.timedelta(hours=duration.hour, |
| minutes=duration.minute, |
| seconds=duration.second) |
| |
| # We must have succeeded copying, save new file path |
| http_fullpath = os.path.join(self.bin_dir, self.html_filename) |
| |
| self.media_url = self.http_server.UrlOf(http_fullpath) |
| |
| video_filename = '%s_%s.%s' % (self.video_name, |
| self.video_def, |
| self.video_format) |
| |
| self.video_source_file = os.path.join(self.remote_golden_image_root_dir, |
| self.video_name, |
| self.video_format, |
| self.video_def, |
| video_filename) |
| |
| |
| @method_logger.log |
| def _load_channel_specs(self): |
| """ |
| Reads channel info configuration file and stores parameters. |
| |
| """ |
| self.parser.read(os.path.join(self.autotest_cros_video_dir, |
| self.channel_spec_filename)) |
| |
| self.samples_per_min = self.parser.getint(self.channel, |
| 'samples_per_min') |
| |
| self.start_capture = datetime.timedelta(seconds=1) |
| |
| duration_in_minutes = self.parser.getfloat(self.channel, |
| 'duration_in_minutes') |
| |
| self.stop_capture = (self.start_capture + |
| datetime.timedelta(minutes=duration_in_minutes)) |
| |
| self.stop_capture = min(self.stop_capture, self.media_length) |
| |
| |
| def make_golden_image_downloader(self): |
| """ |
| @returns a golden image downloader based on configuration data. |
| |
| """ |
| return golden_image_downloader.GoldenImageDownloader( |
| self.test_working_dir, |
| self.remote_golden_image_root_dir, |
| self.video_name, |
| self.video_format, |
| self.video_def, |
| self.device_under_test, |
| screenshot_file_namer.ScreenShotFileNamer( |
| self.screenshot_image_format)) |
| |
| |
| def make_capture_sequence_generator(self): |
| """ |
| Create a (time) sequence generator based on configuration data. |
| |
| Create a random sequence generator if 'random' is specified else create |
| an interval one. |
| |
| Note that we expect the client to specify capture_sequence_style. |
| |
| @returns an object that can generate a sequence of timestamps. |
| |
| """ |
| gn = None |
| style = self.capture_sequence_style |
| |
| if style == 'random': |
| gn = sequence_generator.RandomSequenceGenerator( |
| self.start_capture, |
| self.stop_capture, |
| self.samples_per_min) |
| elif style == 'interval': |
| gn = sequence_generator.IntervalSequenceGenerator( |
| self.start_capture, |
| self.stop_capture, |
| self.capture_interval) |
| |
| return gn |
| |
| |
| def make_chameleon_screenshot_capturer(self, chrome, hostname, args): |
| """ |
| |
| @param chrome: Chrome instance. |
| @param hostname: string, DUT's hostname. |
| @param args: string, arguments passed from autotest command. This |
| typically is the IP of the Chameleon board. |
| |
| @returns a ChameleonScreenshotCapturer object. |
| |
| """ |
| |
| chameleon_board = chameleon.create_chameleon_board(hostname, args) |
| |
| box = (0, self.top_pixels_to_crop, self.screen_width_pixels, |
| self.screen_height_pixels - self.bottom_pixels_to_crop) |
| |
| return chameleon_screenshot_capturer.ChameleonScreenshotCapturer( |
| chameleon_board, |
| self.chameleon_interface, |
| display_utility.DisplayUtility(chrome), |
| self.test_working_dir, |
| self.timeout_video_input_s, |
| box) |
| |
| |
| def make_import_screenshot_capturer(self): |
| """ |
| @returns an ImportScreenShotCapturer configured according to values in |
| the config file |
| |
| """ |
| return import_screenshot_capturer.ImportScreenShotCapturer( |
| self.test_working_dir, |
| self.screen_height_pixels, |
| self.top_pixels_to_crop, |
| self.bottom_pixels_to_crop) |
| |
| |
| def make_video_screenshot_collector(self, capturer): |
| """ |
| Create an object to coordinate navigating video to specific times and |
| taking screenshots. |
| |
| @param capturer: ImportScreenshotCapturer or ChameleonScreenshotCapturer |
| object. |
| @returns an object that accepts timestamps as input and takes |
| screenshots of a video at those times. |
| |
| """ |
| player = media_player.VideoPlayer(self.tab, |
| self.media_url, |
| self.video_source_file, |
| self.media_id, |
| self.time_out_events_s, |
| self.time_btwn_polling_s) |
| |
| namer = screenshot_file_namer.ScreenShotFileNamer( |
| self.screenshot_image_format) |
| |
| return video_screenshot_collector.VideoScreenShotCollector(player, |
| namer, |
| capturer) |