diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py')
-rw-r--r-- | testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py b/testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py new file mode 100644 index 000000000..0b90af0d8 --- /dev/null +++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py @@ -0,0 +1,411 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import ConfigParser +import os +import re + +import mozinfo + +from firefox_puppeteer.base import BaseLib +from firefox_puppeteer.api.appinfo import AppInfo + + +class ActiveUpdate(BaseLib): + + def __getattr__(self, attr): + value = self.marionette.execute_script(""" + let ums = Components.classes['@mozilla.org/updates/update-manager;1'] + .getService(Components.interfaces.nsIUpdateManager); + return ums.activeUpdate[arguments[0]]; + """, script_args=[attr]) + + if value: + return value + else: + raise AttributeError('{} has no attribute {}'.format(self.__class__.__name__, + attr)) + + @property + def exists(self): + """Checks if there is an active update. + + :returns: True if there is an active update + """ + active_update = self.marionette.execute_script(""" + let ums = Components.classes['@mozilla.org/updates/update-manager;1'] + .getService(Components.interfaces.nsIUpdateManager); + return ums.activeUpdate; + """) + + return bool(active_update) + + def get_patch_at(self, patch_index): + """Use nsIUpdate.getPatchAt to return a patch from an update. + + :returns: JSON data for an nsIUpdatePatch object + """ + return self.marionette.execute_script(""" + let ums = Components.classes['@mozilla.org/updates/update-manager;1'] + .getService(Components.interfaces.nsIUpdateManager); + return ums.activeUpdate.getPatchAt(arguments[0]); + """, script_args=[patch_index]) + + @property + def patch_count(self): + """Get the patchCount from the active update. + + :returns: The patch count + """ + return self.marionette.execute_script(""" + let ums = Components.classes['@mozilla.org/updates/update-manager;1'] + .getService(Components.interfaces.nsIUpdateManager); + return ums.activeUpdate.patchCount; + """) + + @property + def selected_patch(self): + """Get the selected patch for the active update. + + :returns: JSON data for the selected patch + """ + return self.marionette.execute_script(""" + let ums = Components.classes['@mozilla.org/updates/update-manager;1'] + .getService(Components.interfaces.nsIUpdateManager); + return ums.activeUpdate.selectedPatch; + """) + + +class MARChannels(BaseLib): + """Class to handle the allowed MAR channels as listed in update-settings.ini.""" + INI_SECTION = 'Settings' + INI_OPTION = 'ACCEPTED_MAR_CHANNEL_IDS' + + class MARConfigParser(ConfigParser.ConfigParser): + """INI parser which allows to read and write MAR config files. + + Virtually identical to the original method, but delimit keys and values + with '=' instead of ' = ' + """ + + def write(self, fp): + """Write an .ini-format representation of the configuration state.""" + if self._defaults: + fp.write("[%s]\n" % ConfigParser.DEFAULTSECT) + for (key, value) in self._defaults.items(): + fp.write("%s=%s\n" % (key, str(value).replace('\n', '\n\t'))) + fp.write("\n") + for section in self._sections: + fp.write("[%s]\n" % section) + for (key, value) in self._sections[section].items(): + if key == "__name__": + continue + if (value is not None) or (self._optcre == self.OPTCRE): + key = "=".join((key, str(value).replace('\n', '\n\t'))) + fp.write("%s\n" % (key)) + fp.write("\n") + + def __init__(self, marionette): + BaseLib.__init__(self, marionette) + + self.config_file_path = self.marionette.execute_script(""" + Components.utils.import('resource://gre/modules/Services.jsm'); + + let file = Services.dirsvc.get('GreD', Components.interfaces.nsIFile); + file.append('update-settings.ini'); + + return file.path; + """) + + self.config = self.MARConfigParser() + self.config.optionxform = str + + @property + def channels(self): + """The currently accepted MAR channels. + + :returns: A set of channel names + """ + # Make sure to always read the current file contents + self.config.read(self.config_file_path) + + return set(self.config.get(self.INI_SECTION, self.INI_OPTION).split(',')) + + @channels.setter + def channels(self, channels): + """Set the accepted MAR channels. + + :param channels: A set of channel names + """ + self.config.set(self.INI_SECTION, self.INI_OPTION, ','.join(channels)) + with open(self.config_file_path, 'wb') as configfile: + self.config.write(configfile) + + def add_channels(self, channels): + """Add additional MAR channels. + + :param channels: A set of channel names to add + """ + self.channels = self.channels | set(channels) + + def remove_channels(self, channels): + """Remove MAR channels. + + :param channels: A set of channel names to remove + """ + self.channels = self.channels - set(channels) + + +class SoftwareUpdate(BaseLib): + """The SoftwareUpdate API adds support for an easy access to the update process.""" + PREF_APP_DISTRIBUTION = 'distribution.id' + PREF_APP_DISTRIBUTION_VERSION = 'distribution.version' + PREF_APP_UPDATE_CHANNEL = 'app.update.channel' + PREF_APP_UPDATE_URL = 'app.update.url' + PREF_APP_UPDATE_URL_OVERRIDE = 'app.update.url.override' + PREF_DISABLED_ADDONS = 'extensions.disabledAddons' + + def __init__(self, marionette): + BaseLib.__init__(self, marionette) + + self.app_info = AppInfo(marionette) + + self._mar_channels = MARChannels(marionette) + self._active_update = ActiveUpdate(marionette) + + @property + def ABI(self): + """Get the customized ABI for the update service. + + :returns: ABI version + """ + abi = self.app_info.XPCOMABI + if mozinfo.isMac: + abi += self.marionette.execute_script(""" + let macutils = Components.classes['@mozilla.org/xpcom/mac-utils;1'] + .getService(Components.interfaces.nsIMacUtils); + if (macutils.isUniversalBinary) { + return '-u-' + macutils.architecturesInBinary; + } + return ''; + """) + + return abi + + @property + def active_update(self): + """ Holds a reference to an :class:`ActiveUpdate` object.""" + return self._active_update + + @property + def allowed(self): + """Check if the user has permissions to run the software update + + :returns: Status if the user has the permissions + """ + return self.marionette.execute_script(""" + let aus = Components.classes['@mozilla.org/updates/update-service;1'] + .getService(Components.interfaces.nsIApplicationUpdateService); + return aus.canCheckForUpdates && aus.canApplyUpdates; + """) + + @property + def build_info(self): + """Return information of the current build version + + :returns: A dictionary of build information + """ + update_url = self.get_update_url(True) + + return { + 'buildid': self.app_info.appBuildID, + 'channel': self.update_channel, + 'disabled_addons': self.marionette.get_pref(self.PREF_DISABLED_ADDONS), + 'locale': self.app_info.locale, + 'mar_channels': self.mar_channels.channels, + 'update_url': update_url, + 'update_snippet': self.get_update_snippet(update_url), + 'user_agent': self.app_info.user_agent, + 'version': self.app_info.version + } + + @property + def is_complete_update(self): + """Return true if the offered update is a complete update + + :returns: True if the offered update is a complete update + """ + # Throw when isCompleteUpdate is called without an update. This should + # never happen except if the test is incorrectly written. + assert self.active_update.exists, 'An active update has been found' + + patch_count = self.active_update.patch_count + assert patch_count == 1 or patch_count == 2,\ + 'An update must have one or two patches included' + + # Ensure Partial and Complete patches produced have unique urls + if patch_count == 2: + patch0_url = self.active_update.get_patch_at(0)['URL'] + patch1_url = self.active_update.get_patch_at(1)['URL'] + assert patch0_url != patch1_url,\ + 'Partial and Complete download URLs are different' + + return self.active_update.selected_patch['type'] == 'complete' + + @property + def mar_channels(self): + """ Holds a reference to a :class:`MARChannels` object.""" + return self._mar_channels + + @property + def os_version(self): + """Returns information about the OS version + + :returns: The OS version + """ + return self.marionette.execute_script(""" + Components.utils.import("resource://gre/modules/Services.jsm"); + + let osVersion; + try { + osVersion = Services.sysinfo.getProperty("name") + " " + + Services.sysinfo.getProperty("version"); + } + catch (ex) { + } + + if (osVersion) { + try { + osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")"; + } + catch (e) { + // Not all platforms have a secondary widget library, + // so an error is nothing to worry about. + } + osVersion = encodeURIComponent(osVersion); + } + return osVersion; + """) + + @property + def patch_info(self): + """ Returns information of the active update in the queue.""" + info = {'channel': self.update_channel} + + if (self.active_update.exists): + info['buildid'] = self.active_update.buildID + info['is_complete'] = self.is_complete_update + info['size'] = self.active_update.selected_patch['size'] + info['type'] = self.update_type + info['url_mirror'] = \ + self.active_update.selected_patch['finalURL'] or 'n/a' + info['version'] = self.active_update.appVersion + + return info + + @property + def staging_directory(self): + """ Returns the path to the updates staging directory.""" + return self.marionette.execute_script(""" + let aus = Components.classes['@mozilla.org/updates/update-service;1'] + .getService(Components.interfaces.nsIApplicationUpdateService); + return aus.getUpdatesDirectory().path; + """) + + @property + def update_channel(self): + """Return the currently used update channel.""" + return self.marionette.get_pref(self.PREF_APP_UPDATE_CHANNEL, + default_branch=True) + + @update_channel.setter + def update_channel(self, channel): + """Set the update channel to be used for update checks. + + :param channel: New update channel to use + + """ + writer = UpdateChannelWriter(self.marionette) + writer.set_channel(channel) + + @property + def update_type(self): + """Returns the type of the active update.""" + return self.active_update.type + + def force_fallback(self): + """Update the update.status file and set the status to 'failed:6'""" + with open(os.path.join(self.staging_directory, 'update.status'), 'w') as f: + f.write('failed: 6\n') + + def get_update_snippet(self, update_url): + """Retrieve contents of the update snippet. + + :param update_url: URL to the update snippet + """ + snippet = None + try: + import urllib2 + response = urllib2.urlopen(update_url) + snippet = response.read() + except Exception: + pass + + return snippet + + def get_update_url(self, force=False): + """Retrieve the AUS update URL the update snippet is retrieved from. + + :param force: Boolean flag to force an update check + + :returns: The URL of the update snippet + """ + url = self.marionette.get_pref(self.PREF_APP_UPDATE_URL_OVERRIDE) + if not url: + url = self.marionette.get_pref(self.PREF_APP_UPDATE_URL) + + # Format the URL by replacing placeholders + url = self.marionette.execute_script(""" + Components.utils.import("resource://gre/modules/UpdateUtils.jsm") + return UpdateUtils.formatUpdateURL(arguments[0]); + """, script_args=[url]) + + if force: + if '?' in url: + url += '&' + else: + url += '?' + url += 'force=1' + + return url + + +class UpdateChannelWriter(BaseLib): + """Class to handle the update channel as listed in channel-prefs.js""" + REGEX_UPDATE_CHANNEL = re.compile(r'("app\.update\.channel", ")([^"].*)(?=")') + + def __init__(self, *args, **kwargs): + BaseLib.__init__(self, *args, **kwargs) + + self.file_path = self.marionette.execute_script(""" + Components.utils.import('resource://gre/modules/Services.jsm'); + + let file = Services.dirsvc.get('PrfDef', Components.interfaces.nsIFile); + file.append('channel-prefs.js'); + + return file.path; + """) + + def set_channel(self, channel): + """Set default update channel. + + :param channel: New default update channel + """ + with open(self.file_path) as f: + file_contents = f.read() + + new_content = re.sub( + self.REGEX_UPDATE_CHANNEL, r'\g<1>' + channel, file_contents) + with open(self.file_path, 'w') as f: + f.write(new_content) |