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 constants
      6 import logging
      7 import traceback
      8 
      9 
     10 class FlagChanger(object):
     11   """Changes the flags Chrome runs with.
     12 
     13   There are two different use cases for this file:
     14   * Flags are permanently set by calling Set().
     15   * Flags can be temporarily set for a particular set of unit tests.  These
     16     tests should call Restore() to revert the flags to their original state
     17     once the tests have completed.
     18   """
     19 
     20   def __init__(self, adb,
     21                cmdline_file=constants.PACKAGE_INFO['chrome'].cmdline_file):
     22     """Initializes the FlagChanger and records the original arguments.
     23 
     24     Args:
     25       adb: An instance of AndroidCommands.
     26       cmdline_file: Path to the command line file on the device.
     27     """
     28     self._adb = adb
     29     self._cmdline_file = cmdline_file
     30 
     31     # Save the original flags.
     32     self._orig_line = self._adb.GetFileContents(self._cmdline_file)
     33     if self._orig_line:
     34       self._orig_line = self._orig_line[0].strip()
     35 
     36     # Parse out the flags into a list to facilitate adding and removing flags.
     37     self._current_flags = self._TokenizeFlags(self._orig_line)
     38 
     39   def Get(self):
     40     """Returns list of current flags."""
     41     return self._current_flags
     42 
     43   def Set(self, flags):
     44     """Replaces all flags on the current command line with the flags given.
     45 
     46     Args:
     47       flags: A list of flags to set, eg. ['--single-process'].
     48     """
     49     if flags:
     50       assert flags[0] != 'chrome'
     51 
     52     self._current_flags = flags
     53     self._UpdateCommandLineFile()
     54 
     55   def AddFlags(self, flags):
     56     """Appends flags to the command line if they aren't already there.
     57 
     58     Args:
     59       flags: A list of flags to add on, eg. ['--single-process'].
     60     """
     61     if flags:
     62       assert flags[0] != 'chrome'
     63 
     64     # Avoid appending flags that are already present.
     65     for flag in flags:
     66       if flag not in self._current_flags:
     67         self._current_flags.append(flag)
     68     self._UpdateCommandLineFile()
     69 
     70   def RemoveFlags(self, flags):
     71     """Removes flags from the command line, if they exist.
     72 
     73     Args:
     74       flags: A list of flags to remove, eg. ['--single-process'].  Note that we
     75              expect a complete match when removing flags; if you want to remove
     76              a switch with a value, you must use the exact string used to add
     77              it in the first place.
     78     """
     79     if flags:
     80       assert flags[0] != 'chrome'
     81 
     82     for flag in flags:
     83       if flag in self._current_flags:
     84         self._current_flags.remove(flag)
     85     self._UpdateCommandLineFile()
     86 
     87   def Restore(self):
     88     """Restores the flags to their original state."""
     89     self._current_flags = self._TokenizeFlags(self._orig_line)
     90     self._UpdateCommandLineFile()
     91 
     92   def _UpdateCommandLineFile(self):
     93     """Writes out the command line to the file, or removes it if empty."""
     94     logging.info('Current flags: %s', self._current_flags)
     95     # Root is not required to write to /data/local/tmp/.
     96     use_root = '/data/local/tmp/' not in self._cmdline_file
     97     if self._current_flags:
     98       # The first command line argument doesn't matter as we are not actually
     99       # launching the chrome executable using this command line.
    100       cmd_line = ' '.join(['_'] + self._current_flags)
    101       if use_root:
    102         self._adb.SetProtectedFileContents(self._cmdline_file, cmd_line)
    103         file_contents = self._adb.GetProtectedFileContents(self._cmdline_file)
    104       else:
    105         self._adb.SetFileContents(self._cmdline_file, cmd_line)
    106         file_contents = self._adb.GetFileContents(self._cmdline_file)
    107       assert len(file_contents) == 1 and file_contents[0] == cmd_line, (
    108           'Failed to set the command line file at %s' % self._cmdline_file)
    109     else:
    110       if use_root:
    111         self._adb.RunShellCommandWithSU('rm ' + self._cmdline_file)
    112       else:
    113         self._adb.RunShellCommand('rm ' + self._cmdline_file)
    114       assert not self._adb.FileExistsOnDevice(self._cmdline_file), (
    115           'Failed to remove the command line file at %s' % self._cmdline_file)
    116 
    117   def _TokenizeFlags(self, 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