# 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 logging
import re

from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import utils
from autotest_lib.server.cros.faft.firmware_test import FirmwareTest


class firmware_Mosys(FirmwareTest):
    """
    Mosys commands test for Firmware values.

    Execute
    a. mosys -k smbios info bios
    b. mosys -k ec info
    c. mosys platform name
    d. mosys eeprom map
    e. mosys platform vendor
    f. mosys -k pd info

    """
    version = 1


    def initialize(self, host, cmdline_args, dev_mode=False):
        # Parse arguments from command line
        dict_args = utils.args_to_dict(cmdline_args)
        super(firmware_Mosys, self).initialize(host, cmdline_args)
        self.switcher.setup_mode('dev' if dev_mode else 'normal')
        # a list contain failed execution.
        self.failed_command = []
        # Get a list of available mosys commands.
        lines = self.run_cmd('mosys help')
        self.command_list = []
        cmdlist_start = False
        for line in lines:
            if cmdlist_start:
                cmdlst = re.split('\s+', line)
                if len(cmdlst) > 2:
                    self.command_list.append(cmdlst[1])
            elif 'Commands:' in line:
                cmdlist_start = True
        logging.info('Availabe commands: %s', ' '.join(self.command_list))

    def run_cmd(self, command):
        """
        Log and execute command and return the output.

        @param command: Command to executeon device.
        @returns the output of command.

        """
        logging.info('Execute %s', command)
        output = self.faft_client.system.run_shell_command_get_output(command)
        logging.info('Output %s', output)
        return output

    def check_ec_version(self, command, exp_ec_version):
        """
        Compare output of 'ectool version' for the current firmware
        copy to exp_ec_version.

        @param command: command string
        @param exp_ec_version: The exepected EC version string.

        """
        if self.faft_client.system.has_host():
            lines = self.run_cmd('fwtool ec version')
        else:
            lines = self.run_cmd('ectool version')
        fwcopy_pattern = re.compile('Firmware copy: (.*)$')
        ver_pattern = re.compile('(R[OW]) version:    (.*)$')
        version = {}
        for line in lines:
            ver_matched = ver_pattern.match(line)
            if ver_matched:
                version[ver_matched.group(1)] = ver_matched.group(2)
            fwcopy_matched = fwcopy_pattern.match(line)
            if fwcopy_matched:
                fwcopy = fwcopy_matched.group(1)
        if fwcopy in version:
            actual_version = version[fwcopy]
            logging.info('Expected ec version %s actual_version %s',
                         exp_ec_version, actual_version)
            if exp_ec_version != actual_version:
               self._tag_failure(command)
        else:
            self._tag_failure(command)
            logging.error('Failed to locate version from ectool')

    def check_pd_version(self, command, exp_pd_version):
        """
        Compare output of 'ectool --dev 1 version' for the current PD firmware
        copy to exp_pd_version.

        @param command: command string
        @param exp_pd_version: The exepected PD version string.

        """
        lines = self.run_cmd('ectool --dev 1 version')
        fwcopy_pattern = re.compile('Firmware copy: (.*)$')
        ver_pattern = re.compile('(R[OW]) version:    (.*)$')
        version = {}
        for line in lines:
            ver_matched = ver_pattern.match(line)
            if ver_matched:
                version[ver_matched.group(1)] = ver_matched.group(2)
            fwcopy_matched = fwcopy_pattern.match(line)
            if fwcopy_matched:
                fwcopy = fwcopy_matched.group(1)
        if fwcopy in version:
            actual_version = version[fwcopy]
            logging.info('Expected pd version %s actual_version %s',
                         exp_pd_version, actual_version)
            if exp_pd_version != actual_version:
               self._tag_failure(command)
        else:
            self._tag_failure(command)
            logging.error('Failed to locate version from ectool')

    def check_lsb_info(self, command, fieldname, exp_value):
        """
        Comapre output of fieldname in /etc/lsb-release to exp_value.

        @param command: command string
        @param fieldname: field name in lsd-release file.
        @param exp_value: expected value for fieldname

        """
        lsb_info = 'cat /etc/lsb-release'
        lines = self.run_cmd(lsb_info)
        pattern = re.compile(fieldname + '=(.*)$')
        for line in lines:
            matched = pattern.match(line)
            if matched:
                actual = matched.group(1)
                logging.info('Expected %s %s actual %s',
                             fieldname, exp_value, actual)
                # Some board will have prefix.  Example nyan_big for big.
                if exp_value.lower() in actual.lower():
                  return
        self._tag_failure(command)

    def check_adb_devices(self, command, fieldname, exp_value):
        """
        Compare output of fieldname in adb devices -l to exp_value.

        @param command: command string
        @param fieldname: field name from adb devices -l output.
        @param exp_value: expected value for fieldname

        """
        device_info = 'adb devices -l'
        lines = self.faft_client.host.run_shell_command_get_output(device_info)
        logging.info(lines)
        pattern = re.compile(fieldname + ':(\S+)\s+')
        logging.info(pattern)
        for line in lines:
            matched = pattern.search(line)
            if matched:
                actual = matched.group(1)
                logging.info('Expected %s %s actual %s',
                             fieldname, exp_value, actual)
                # Some board will have prefix.  Example nyan_big for big.
                if exp_value.lower() in actual.lower():
                  return
        self._tag_failure(command)

    def _tag_failure(self, cmd):
        self.failed_command.append(cmd)
        logging.error('Execute %s failed', cmd)

    def run_once(self, dev_mode=False):
        # a. mosys -k smbios info bios
        command = 'mosys -k smbios info bios'
        if 'smbios' in self.command_list:
            output = self.run_cmd(command)[0]
            p = re.compile('vendor="coreboot" version="(.*)"'
                           ' release_date="[/0-9]+" size="[0-9]+ KB"')
            v = p.match(output)
            if not v:
              self._tag_failure(command)
            version = v.group(1)
            if not self.checkers.crossystem_checker({'fwid': version}):
              self._tag_failure(command)
        else:
            logging.warning('Skip "%s", command not available.', command)

        # b. mosys -k ec info
        command = 'mosys -k ec info'
        if self.faft_config.chrome_ec:
          output = self.run_cmd(command)[0]
          p = re.compile(
            'vendor="[A-Z]?[a-z]+" name="[ -~]+" fw_version="(.*)"')
          v = p.match(output)
          if v:
             version = v.group(1)
             self.check_ec_version(command, version)
          else:
            self._tag_failure(command)
        else:
          logging.info('Skip "%s", command not available.', command)

        # c. mosys platform name
        command = 'mosys platform name'
        output = self.run_cmd(command)[0]
        if self.faft_client.system.has_host():
            self.check_adb_devices(command, 'product', output)
        else:
            self.check_lsb_info(command, 'CHROMEOS_RELEASE_BOARD', output)

        # d. mosys eeprom map
        command = 'mosys eeprom map|egrep "RW_SHARED|RW_SECTION_[AB]"'
        lines = self.run_cmd(command)
        if len(lines) != 3:
          logging.error('Expect RW_SHARED|RW_SECTION_[AB] got "%s"', lines)
          self._tag_failure(command)
        emap = {'RW_SECTION_A': 0, 'RW_SECTION_B': 0, 'RW_SHARED': 0}
        for line in lines:
            row = line.split(' | ')
            if row[1] in emap:
                emap[row[1]] += 1
            if row[2] == '0x00000000':
                logging.error('Expect non zero but got %s instead(%s)',
                              (row[2], line))
                self._tag_failure(command)
            if row[3] == '0x00000000':
                logging.error('Expect non zero but got %s instead(%s)',
                              (row[3], line))
                self._tag_failure(command)
        # Check that there are one A and one B.
        if emap['RW_SECTION_A'] != 1 or emap['RW_SECTION_B'] != 1:
            logging.error('Missing RW_SECTION A or B, %s', lines)
            self._tag_failure(command)

        # e. mosys platform vendor
        # Output will be GOOGLE until launch, see crosbug/p/29755
        command = 'mosys platform vendor'
        output = self.run_cmd(command)[0]
        p = re.compile('^[-\w\s]+$')
        if not p.match(output):
            logging.error('output is not a string Expect GOOGLE'
                          'or name of maker.')
            self._tag_failure(command)

        # f. mosys -k pd info
        command = 'mosys -k pd info'
        if self.faft_config.chrome_usbpd and 'pd' in self.command_list:
          output = self.run_cmd(command)[0]
          p = re.compile('vendor="[a-z]+" name="[ -~]+" fw_version="(.*)"')
          v = p.match(output)
          if v:
             version = v.group(1)
             self.check_pd_version(command, version)
          else:
             self._tag_failure(command)
        else:
          logging.info('Skip "%s", command not available.', command)

        # g. mosys -k memory spd print all (check no error output)
        command = 'mosys -k memory spd print all'
        output = self.run_cmd(command)
        p = re.compile('^dimm=".*$')
        # Each line should start with "dimm=".
        for i in output:
            if not p.match(i):
                logging.error('output does not start with dimm=%s', i)
                self._tag_failure(command)
                break

        # Add any other mosys commands or tests before this section.
        # empty failed_command indicate all passed.
        if self.failed_command:
          raise error.TestFail('%d commands failed, detail above.  '
                               'Failed commands are "%s"' %
                               (len(self.failed_command),
                               ','.join(self.failed_command)))