flag_changer.py revision 645501c2ab19a559ce82a1d5a29ced159a4c30fb
1645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez# Use of this source code is governed by a BSD-style license that can be 3645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez# found in the LICENSE file. 4645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 5645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezimport logging 6645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 7645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezfrom devil.android import device_errors 8645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 9645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 10645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass FlagChanger(object): 11645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Changes the flags Chrome runs with. 12645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 13645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Flags can be temporarily set for a particular set of unit tests. These 14645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez tests should call Restore() to revert the flags to their original state 15645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez once the tests have completed. 16645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """ 17645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 18645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez def __init__(self, device, cmdline_file): 19645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Initializes the FlagChanger and records the original arguments. 20645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 21645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Args: 22645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez device: A DeviceUtils instance. 23645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez cmdline_file: Path to the command line file on the device. 24645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """ 25645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._device = device 26645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 27645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # Unrooted devices have limited access to the file system. 28645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # Place files in /data/local/tmp/ rather than /data/local/ 29645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if not device.HasRoot() and not '/data/local/tmp/' in cmdline_file: 30645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._cmdline_file = cmdline_file.replace('/data/local/', 31645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez '/data/local/tmp/') 32645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez else: 33645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._cmdline_file = cmdline_file 34645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 35645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez stored_flags = '' 36645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if self._device.PathExists(self._cmdline_file): 37645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez try: 38645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez stored_flags = self._device.ReadFile(self._cmdline_file).strip() 39645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez except device_errors.CommandFailedError: 40645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez pass 41645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # Store the flags as a set to facilitate adding and removing flags. 42645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._state_stack = [set(self._TokenizeFlags(stored_flags))] 43645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 44645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez def ReplaceFlags(self, flags): 45645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Replaces the flags in the command line with the ones provided. 46645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Saves the current flags state on the stack, so a call to Restore will 47645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez change the state back to the one preceeding the call to ReplaceFlags. 48645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 49645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Args: 50645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez flags: A sequence of command line flags to set, eg. ['--single-process']. 51645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Note: this should include flags only, not the name of a command 52645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez to run (ie. there is no need to start the sequence with 'chrome'). 53645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """ 54645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez new_flags = set(flags) 55645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._state_stack.append(new_flags) 56645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._UpdateCommandLineFile() 57645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 58645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez def AddFlags(self, flags): 59645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Appends flags to the command line if they aren't already there. 60645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Saves the current flags state on the stack, so a call to Restore will 61645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez change the state back to the one preceeding the call to AddFlags. 62645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 63645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Args: 64645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez flags: A sequence of flags to add on, eg. ['--single-process']. 65645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """ 66645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self.PushFlags(add=flags) 67645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 68645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez def RemoveFlags(self, flags): 69645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Removes flags from the command line, if they exist. 70645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Saves the current flags state on the stack, so a call to Restore will 71645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez change the state back to the one preceeding the call to RemoveFlags. 72645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 73645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Note that calling RemoveFlags after AddFlags will result in having 74645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez two nested states. 75645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 76645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Args: 77645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez flags: A sequence of flags to remove, eg. ['--single-process']. Note 78645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez that we expect a complete match when removing flags; if you want 79645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez to remove a switch with a value, you must use the exact string 80645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez used to add it in the first place. 81645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """ 82645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self.PushFlags(remove=flags) 83645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 84645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez def PushFlags(self, add=None, remove=None): 85645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Appends and removes flags to/from the command line if they aren't already 86645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez there. Saves the current flags state on the stack, so a call to Restore 87645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez will change the state back to the one preceeding the call to PushFlags. 88645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 89645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Args: 90645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez add: A list of flags to add on, eg. ['--single-process']. 91645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez remove: A list of flags to remove, eg. ['--single-process']. Note that we 92645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez expect a complete match when removing flags; if you want to remove 93645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez a switch with a value, you must use the exact string used to add 94645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez it in the first place. 95645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """ 96645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez new_flags = self._state_stack[-1].copy() 97645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if add: 98645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez new_flags.update(add) 99645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if remove: 100645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez new_flags.difference_update(remove) 101645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self.ReplaceFlags(new_flags) 102645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 103645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez def Restore(self): 104645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Restores the flags to their state prior to the last AddFlags or 105645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez RemoveFlags call. 106645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """ 107645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # The initial state must always remain on the stack. 108645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez assert len(self._state_stack) > 1, ( 109645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez "Mismatch between calls to Add/RemoveFlags and Restore") 110645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._state_stack.pop() 111645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._UpdateCommandLineFile() 112645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 113645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez def _UpdateCommandLineFile(self): 114645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Writes out the command line to the file, or removes it if empty.""" 115645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez current_flags = list(self._state_stack[-1]) 116645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez logging.info('Current flags: %s', current_flags) 117645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # Root is not required to write to /data/local/tmp/. 118645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez use_root = '/data/local/tmp/' not in self._cmdline_file 119645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if current_flags: 120645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # The first command line argument doesn't matter as we are not actually 121645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # launching the chrome executable using this command line. 122645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez cmd_line = ' '.join(['_'] + current_flags) 123645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._device.WriteFile( 124645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._cmdline_file, cmd_line, as_root=use_root) 125645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez file_contents = self._device.ReadFile( 126645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._cmdline_file, as_root=use_root).rstrip() 127645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez assert file_contents == cmd_line, ( 128645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 'Failed to set the command line file at %s' % self._cmdline_file) 129645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez else: 130645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez self._device.RunShellCommand('rm ' + self._cmdline_file, 131645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez as_root=use_root) 132645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez assert not self._device.FileExists(self._cmdline_file), ( 133645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 'Failed to remove the command line file at %s' % self._cmdline_file) 134645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 135645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez @staticmethod 136645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez def _TokenizeFlags(line): 137645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """Changes the string containing the command line into a list of flags. 138645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 139645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Follows similar logic to CommandLine.java::tokenizeQuotedArguments: 140645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez * Flags are split using whitespace, unless the whitespace is within a 141645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez pair of quotation marks. 142645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez * Unlike the Java version, we keep the quotation marks around switch 143645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez values since we need them to re-create the file when new flags are 144645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez appended. 145645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 146645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez Args: 147645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez line: A string containing the entire command line. The first token is 148645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez assumed to be the program name. 149645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez """ 150645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if not line: 151645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez return [] 152645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 153645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez tokenized_flags = [] 154645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez current_flag = "" 155645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez within_quotations = False 156645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 157645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # Move through the string character by character and build up each flag 158645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # along the way. 159645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez for c in line.strip(): 160645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if c is '"': 161645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if len(current_flag) > 0 and current_flag[-1] == '\\': 162645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # Last char was a backslash; pop it, and treat this " as a literal. 163645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez current_flag = current_flag[0:-1] + '"' 164645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez else: 165645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez within_quotations = not within_quotations 166645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez current_flag += c 167645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez elif not within_quotations and (c is ' ' or c is '\t'): 168645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if current_flag is not "": 169645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez tokenized_flags.append(current_flag) 170645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez current_flag = "" 171645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez else: 172645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez current_flag += c 173645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 174645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # Tack on the last flag. 175645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if not current_flag: 176645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez if within_quotations: 177645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez logging.warn('Unterminated quoted argument: ' + line) 178645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez else: 179645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez tokenized_flags.append(current_flag) 180645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez 181645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez # Return everything but the program name. 182645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez return tokenized_flags[1:] 183