Chromium Code Reviews| Index: chrome/tools/test/experiment_tool_win.py |
| diff --git a/chrome/tools/test/experiment_tool_win.py b/chrome/tools/test/experiment_tool_win.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..e52fe30f52f1841565a8699462b5e690587d915a |
| --- /dev/null |
| +++ b/chrome/tools/test/experiment_tool_win.py |
| @@ -0,0 +1,168 @@ |
| +#!/usr/bin/env python |
| +# Copyright 2017 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +# A tool for manipulating the user retention experiment state for a Chrome |
| +# install. |
| +# |
| +# usage: experiment_tool_win.py [-h] --operation {clean,prep} |
| +# [--channel {stable,beta,dev,canary}] |
| +# [--system-level] |
| +# |
| +# A helper for working with state associated with the M60 Chrome on Windows 10 |
| +# retention experiment. |
| +# |
| +# optional arguments: |
| +# -h, --help show this help message and exit |
| +# --operation {clean,prep} |
| +# The operation to be performed. |
| +# --channel {stable,beta,dev,canary} |
| +# The install on which to operate (stable by default). |
| +# --system-level Specify to operate on a system-level install (user- |
| +# level by default). |
| + |
| +import argparse |
| +import math |
| +import sys |
| +from datetime import datetime, timedelta |
| +import win32api |
| +import win32security |
| +import _winreg |
| + |
| + |
| +def GetUserSidString(): |
| + """Returns the current user's SID string.""" |
| + token_handle = win32security.OpenProcessToken(win32api.GetCurrentProcess(), |
| + win32security.TOKEN_QUERY) |
| + user_sid, _ = win32security.GetTokenInformation(token_handle, |
| + win32security.TokenUser) |
| + return win32security.ConvertSidToStringSid(user_sid) |
| + |
| + |
| +def InternalTimeFromPyTime(pytime): |
| + """Returns a Chromium internal time value representing a Python datetime.""" |
| + # Microseconds since 1601-01-01 00:00:00 UTC |
| + delta = pytime - datetime(1601, 1, 1) |
| + return math.trunc(delta.total_seconds()) * 1000000 + delta.microseconds |
| + |
| + |
| +class ChromeState: |
| + """An object that provides mutations on Chrome's state relating to the |
| + user experiment. |
| + """ |
| + _CHANNEL_CONFIGS = { |
| + 'stable': { |
| + 'guid': '{8A69D345-D564-463c-AFF1-A69D9E530F96}' |
| + }, |
| + 'beta': { |
| + 'guid': '{8237E44A-0054-442C-B6B6-EA0509993955}' |
| + }, |
| + 'dev': { |
| + 'guid': '{401C381F-E0DE-4B85-8BD8-3F3F14FBDA57}' |
| + }, |
| + 'canary': { |
| + 'guid': '{4ea16ac7-fd5a-47c3-875b-dbf4a2008c20}' |
| + }, |
| + } |
| + _GOOGLE_UPDATE_PATH = 'Software\\Google\\Update' |
| + |
| + def __init__(self, channel_name, system_level): |
| + self._config = ChromeState._CHANNEL_CONFIGS[channel_name] |
| + self._system_level = system_level |
| + self._registry_root = _winreg.HKEY_LOCAL_MACHINE if self._system_level \ |
| + else _winreg.HKEY_CURRENT_USER |
| + |
| + def DeleteRetentionStudyValue(self): |
| + """Deletes the RetntionStudy value for the install.""" |
|
Patrick Monette
2017/06/13 19:13:40
Typo
grt (UTC plus 2)
2017/06/14 20:43:44
Done.
|
| + path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState\\' + \ |
| + self._config['guid'] |
| + try: |
| + with _winreg.OpenKey(self._registry_root, path, 0, |
| + _winreg.KEY_WOW64_32KEY | |
| + _winreg.KEY_SET_VALUE) as key: |
| + _winreg.DeleteValue(key, 'RetentionStudy') |
| + except WindowsError as error: |
| + if error.winerror != 2: |
| + raise |
| + |
| + def DeleteExperimentLabelsValue(self): |
| + """Deletes the experiment_labels for the install.""" |
| + medium = 'Medium' if self._system_level else '' |
| + path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState' + medium + '\\' + \ |
| + self._config['guid'] |
| + try: |
| + with _winreg.OpenKey(self._registry_root, path, 0, |
| + _winreg.KEY_WOW64_32KEY | |
| + _winreg.KEY_SET_VALUE) as key: |
| + _winreg.DeleteValue(key, 'experiment_labels') |
| + except WindowsError as error: |
| + if error.winerror != 2: |
| + raise |
| + |
| + def DeleteRentionKey(self): |
| + """Deletes the Retention key for the current user.""" |
| + medium = 'Medium' if self._system_level else '' |
| + path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState' + medium + '\\' + \ |
| + self._config['guid'] + '\\Retention' |
| + try: |
| + if self._system_level: |
| + _winreg.DeleteKeyEx(self._registry_root, |
| + path + '\\' + GetUserSidString(), |
| + _winreg.KEY_WOW64_32KEY) |
| + _winreg.DeleteKeyEx(self._registry_root, path, _winreg.KEY_WOW64_32KEY) |
| + except WindowsError as error: |
| + if error.winerror != 2: |
| + raise |
| + |
| + def SetLastRunTime(self, delta): |
| + """Sets Chrome's lastrun time for the current user.""" |
| + path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState\\' + \ |
| + self._config['guid'] |
| + lastrun = InternalTimeFromPyTime(datetime.utcnow() - delta) |
| + with _winreg.CreateKeyEx(_winreg.HKEY_CURRENT_USER, path, 0, |
| + _winreg.KEY_WOW64_32KEY | |
| + _winreg.KEY_SET_VALUE) as key: |
| + _winreg.SetValueEx(key, 'lastrun', 0, _winreg.REG_SZ, str(lastrun)) |
| + |
| + |
| +def DoClean(chrome_state): |
| + """Deletes all state associated with the user experiment.""" |
| + chrome_state.DeleteRetentionStudyValue() |
| + chrome_state.DeleteExperimentLabelsValue() |
| + chrome_state.DeleteRentionKey() |
| + return 0 |
| + |
| + |
| +def DoPrep(chrome_state): |
| + """Prepares an install for participation in the experiment.""" |
| + # Clear old state. |
| + DoClean(chrome_state) |
| + # Make Chrome appear to have been last run 30 days ago. |
| + chrome_state.SetLastRunTime(timedelta(30)) |
| + return 0 |
| + |
| + |
| +def main(options): |
| + chrome_state = ChromeState(options.channel, options.system_level) |
| + if options.operation == 'clean': |
| + return DoClean(chrome_state) |
| + if options.operation == 'prep': |
| + return DoPrep(chrome_state) |
| + return 1 |
| + |
| + |
| +if __name__ == '__main__': |
| + parser = argparse.ArgumentParser( |
| + description='A helper for working with state associated with the M60 ' |
| + 'Chrome on Windows 10 retention experiment.') |
| + parser.add_argument('--operation', required=True, choices=['clean', 'prep'], |
| + help='The operation to be performed.') |
| + parser.add_argument('--channel', default='stable', |
| + choices=['stable', 'beta', 'dev', 'canary'], |
| + help='The install on which to operate (stable by ' \ |
| + 'default).') |
| + parser.add_argument('--system-level', action='store_true', |
| + help='Specify to operate on a system-level install ' \ |
| + '(user-level by default).') |
| + sys.exit(main(parser.parse_args())) |