Home | History | Annotate | Download | only in pylib
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 
      7 import pylib.android_commands
      8 import pylib.device.device_utils
      9 
     10 
     11 class FlagChanger(object):
     12   """Changes the flags Chrome runs with.
     13 
     14   There are two different use cases for this file:
     15   * Flags are permanently set by calling Set().
     16   * Flags can be temporarily set for a particular set of unit tests.  These
     17     tests should call Restore() to revert the flags to their original state
     18     once the tests have completed.
     19   """
     20 
     21   def __init__(self, device, cmdline_file):
     22     """Initializes the FlagChanger and records the original arguments.
     23 
     24     Args:
     25       device: A DeviceUtils instance.
     26       cmdline_file: Path to the command line file on the device.
     27     """
     28     # TODO(jbudorick) Remove once telemetry switches over.
     29     if isinstance(device, pylib.android_commands.AndroidCommands):
     30       device = pylib.device.device_utils.DeviceUtils(device)
     31     self._device = device
     32     self._cmdline_file = cmdline_file
     33 
     34     # Save the original flags.
     35     self._orig_line = self._device.ReadFile(self._cmdline_file)
     36     if self._orig_line:
     37       self._orig_line = self._orig_line[0].strip()
     38 
     39     # Parse out the flags into a list to facilitate adding and removing flags.
     40     self._current_flags = self._TokenizeFlags(self._orig_line)
     41 
     42   def Get(self):
     43     """Returns list of current flags."""
     44     return self._current_flags
     45 
     46   def Set(self, flags):
     47     """Replaces all flags on the current command line with the flags given.
     48 
     49     Args:
     50       flags: A list of flags to set, eg. ['--single-process'].
     51     """
     52     if flags:
     53       assert flags[0] != 'chrome'
     54 
     55     self._current_flags = flags
     56     self._UpdateCommandLineFile()
     57 
     58   def AddFlags(self, flags):
     59     """Appends flags to the command line if they aren't already there.
     60 
     61     Args:
     62       flags: A list of flags to add on, eg. ['--single-process'].
     63     """
     64     if flags:
     65       assert flags[0] != 'chrome'
     66 
     67     # Avoid appending flags that are already present.
     68     for flag in flags:
     69       if flag not in self._current_flags:
     70         self._current_flags.append(flag)
     71     self._UpdateCommandLineFile()
     72 
     73   def RemoveFlags(self, flags):
     74     """Removes flags from the command line, if they exist.
     75 
     76     Args:
     77       flags: A list of flags to remove, eg. ['--single-process'].  Note that we
     78              expect a complete match when removing flags; if you want to remove
     79              a switch with a value, you must use the exact string used to add
     80              it in the first place.
     81     """
     82     if flags:
     83       assert flags[0] != 'chrome'
     84 
     85     for flag in flags:
     86       if flag in self._current_flags:
     87         self._current_flags.remove(flag)
     88     self._UpdateCommandLineFile()
     89 
     90   def Restore(self):
     91     """Restores the flags to their original state."""
     92     self._current_flags = self._TokenizeFlags(self._orig_line)
     93     self._UpdateCommandLineFile()
     94 
     95   def _UpdateCommandLineFile(self):
     96     """Writes out the command line to the file, or removes it if empty."""
     97     logging.info('Current flags: %s', self._current_flags)
     98     # Root is not required to write to /data/local/tmp/.
     99     use_root = '/data/local/tmp/' not in self._cmdline_file
    100     if self._current_flags:
    101       # The first command line argument doesn't matter as we are not actually
    102       # launching the chrome executable using this command line.
    103       cmd_line = ' '.join(['_'] + self._current_flags)
    104       self._device.WriteFile(
    105           self._cmdline_file, cmd_line, as_root=use_root)
    106       file_contents = self._device.ReadFile(
    107           self._cmdline_file, as_root=use_root)
    108       assert len(file_contents) == 1 and file_contents[0] == cmd_line, (
    109           'Failed to set the command line file at %s' % self._cmdline_file)
    110     else:
    111       self._device.RunShellCommand('rm ' + self._cmdline_file,
    112                                    as_root=use_root)
    113       assert not self._device.FileExists(self._cmdline_file), (
    114           'Failed to remove the command line file at %s' % self._cmdline_file)
    115 
    116   @staticmethod
    117   def _TokenizeFlags(line):
    118     """Changes the string containing the command line into a list of flags.
    119 
    120     Follows similar logic to CommandLine.java::tokenizeQuotedArguments:
    121     * Flags are split using whitespace, unless the whitespace is within a
    122       pair of quotation marks.
    123     * Unlike the Java version, we keep the quotation marks around switch
    124       values since we need them to re-create the file when new flags are
    125       appended.
    126 
    127     Args:
    128       line: A string containing the entire command line.  The first token is
    129             assumed to be the program name.
    130     """
    131     if not line:
    132       return []
    133 
    134     tokenized_flags = []
    135     current_flag = ""
    136     within_quotations = False
    137 
    138     # Move through the string character by character and build up each flag
    139     # along the way.
    140     for c in line.strip():
    141       if c is '"':
    142         if len(current_flag) > 0 and current_flag[-1] == '\\':
    143           # Last char was a backslash; pop it, and treat this " as a literal.
    144           current_flag = current_flag[0:-1] + '"'
    145         else:
    146           within_quotations = not within_quotations
    147           current_flag += c
    148       elif not within_quotations and (c is ' ' or c is '\t'):
    149         if current_flag is not "":
    150           tokenized_flags.append(current_flag)
    151           current_flag = ""
    152       else:
    153         current_flag += c
    154 
    155     # Tack on the last flag.
    156     if not current_flag:
    157       if within_quotations:
    158         logging.warn('Unterminated quoted argument: ' + line)
    159     else:
    160       tokenized_flags.append(current_flag)
    161 
    162     # Return everything but the program name.
    163     return tokenized_flags[1:]
    164