Bug 1142805 - Use ConfigParser to modify update-settings.ini draft
authorHenrik Skupin <mail@hskupin.info>
Tue, 22 Nov 2016 14:49:46 +0100
changeset 442523 404a6893772434cc6d978627eaedb09b7ca5a813
parent 442522 20ce1f710f82e2e0c6f3aecb76caaef9515ad511
child 442524 3ddd10e4a065e14a0069b0877ee6983bd41f3ecb
push id36715
push userbmo:hskupin@gmail.com
push dateTue, 22 Nov 2016 17:00:17 +0000
bugs1142805
milestone53.0a1
Bug 1142805 - Use ConfigParser to modify update-settings.ini MozReview-Commit-ID: KbJofXTDa9p
testing/firefox-ui/harness/firefox_ui_harness/testcases.py
testing/puppeteer/firefox/firefox_puppeteer/api/software_update.py
--- a/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
+++ b/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
@@ -32,50 +32,42 @@ class UpdateTestCase(PuppeteerMixin, Mar
     # http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/update/nsUpdateService.js?rev=a9240b1eb2fb#4813
     # http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/update/nsUpdateService.js?rev=a9240b1eb2fb#4756
     PREF_APP_UPDATE_ALTWINDOWTYPE = 'app.update.altwindowtype'
 
     def __init__(self, *args, **kwargs):
         super(UpdateTestCase, self).__init__(*args, **kwargs)
 
         self.update_channel = kwargs.pop('update_channel')
+        self.update_mar_channels = set(kwargs.pop('update_mar_channels'))
         self.update_url = kwargs.pop('update_url')
 
         self.target_buildid = kwargs.pop('update_target_buildid')
         self.target_version = kwargs.pop('update_target_version')
 
-        self.update_mar_channels = set(kwargs.pop('update_mar_channels'))
-        self.default_mar_channels = None
+        # Bug 604364 - Preparation to test multiple update steps
+        self.current_update_index = 0
 
+        self.download_duration = None
         self.updates = []
 
     def setUp(self, is_fallback=False):
         super(UpdateTestCase, self).setUp()
 
         self.software_update = SoftwareUpdate(self.marionette)
-        self.download_duration = None
 
-        # Bug 604364 - Preparation to test multiple update steps
-        self.current_update_index = 0
+        # If requested modify the list of allowed MAR channels
+        if self.update_mar_channels:
+            self.software_update.mar_channels.add_channels(self.update_mar_channels)
 
         # Ensure that there exists no already partially downloaded update
         self.remove_downloaded_update()
 
         self.set_preferences_defaults()
 
-        # If requested modify the list of allowed MAR channels
-        # Bug 1142805 - Modify file via Python directly
-        if self.update_mar_channels:
-            # Backup the original content and the path of the update-settings.ini file
-            self.default_mar_channels = {
-                'content': self.software_update.mar_channels.config_file_contents,
-                'path': self.software_update.mar_channels.config_file_path,
-            }
-            self.software_update.mar_channels.add_channels(self.update_mar_channels)
-
         # Bug 1142805 - Until we don't modify the channel-prefs.js and update-settings.ini
         # files before Firefox gets started, a restart of Firefox is necessary to
         # accept the new update channel.
         self.restart()
 
         # Dictionary which holds the information for each update
         self.updates = [{
             'build_pre': self.software_update.build_info,
@@ -106,18 +98,16 @@ class UpdateTestCase(PuppeteerMixin, Mar
             output = pprint.pformat(self.updates)
             self.logger.info('Update test results: \n{}'.format(output))
         finally:
             super(UpdateTestCase, self).tearDown()
 
             # Ensure that no trace of an partially downloaded update remain
             self.remove_downloaded_update()
 
-            self.restore_config_files()
-
     @property
     def patch_info(self):
         """ Returns information about the active update in the queue.
 
         :returns: A dictionary with information about the active patch
         """
         patch = self.software_update.patch_info
         patch['download_duration'] = self.download_duration
@@ -360,28 +350,16 @@ class UpdateTestCase(PuppeteerMixin, Mar
 
     def restart(self, *args, **kwargs):
         super(UpdateTestCase, self).restart(*args, **kwargs)
 
         # After a restart default preference values as set in the former session are lost.
         # Make sure that any of those are getting restored.
         self.set_preferences_defaults()
 
-    def restore_config_files(self):
-        # Reset update-settings.ini file if modified
-        try:
-            if self.default_mar_channels:
-                path = self.default_mar_channels['path']
-                self.logger.info('Restoring mar channel defaults for: {}'.format(path))
-                with open(path, 'w') as f:
-                    f.write(self.default_mar_channels['content'])
-        except IOError:
-            self.logger.error('Failed to reset the default mar channels.',
-                              exc_info=True)
-
     def set_preferences_defaults(self):
         """Set the default value for specific preferences to force its usage."""
         if self.update_channel:
             self.software_update.update_channel = self.update_channel
         if self.update_url:
             self.software_update.update_url = self.update_url
 
     def wait_for_download_finished(self, window, timeout=TIMEOUT_UPDATE_DOWNLOAD):
--- a/testing/puppeteer/firefox/firefox_puppeteer/api/software_update.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/api/software_update.py
@@ -1,12 +1,13 @@
 # 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 mozinfo
 
 from firefox_puppeteer.base import BaseLib
 from firefox_puppeteer.api.appinfo import AppInfo
 from firefox_puppeteer.api.prefs import Preferences
 
@@ -76,88 +77,85 @@ class ActiveUpdate(BaseLib):
     """)
 
 
 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._ini_file_path = self.marionette.execute_script("""
+        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;
         """)
 
-    @property
-    def config_file_path(self):
-        """The path to the update-settings.ini file."""
-        return self._ini_file_path
-
-    @property
-    def config_file_contents(self):
-        """The contents of the update-settings.ini file."""
-        with open(self.config_file_path) as f:
-            return f.read()
+        self.config = self.MARConfigParser()
+        self.config.optionxform = str
 
     @property
     def channels(self):
-        """The channels as found in the ACCEPTED_MAR_CHANNEL_IDS option
-        of the update-settings.ini file.
+        """The currently accepted MAR channels.
 
         :returns: A set of channel names
         """
-        channels = self.marionette.execute_script("""
-          Components.utils.import("resource://gre/modules/FileUtils.jsm");
-          let iniFactory = Components.classes['@mozilla.org/xpcom/ini-processor-factory;1']
-                           .getService(Components.interfaces.nsIINIParserFactory);
+        # Make sure to always read the current file contents
+        self.config.read(self.config_file_path)
 
-          let file = new FileUtils.File(arguments[0]);
-          let parser = iniFactory.createINIParser(file);
-
-          return parser.getString(arguments[1], arguments[2]);
-        """, script_args=[self.config_file_path, self.INI_SECTION, self.INI_OPTION])
-        return set(channels.split(','))
+        return set(self.config.get(self.INI_SECTION, self.INI_OPTION).split(','))
 
     @channels.setter
     def channels(self, channels):
-        """Set the channels in the update-settings.ini file.
+        """Set the accepted MAR channels.
 
         :param channels: A set of channel names
         """
-        new_channels = ','.join(channels)
-        self.marionette.execute_script("""
-          Components.utils.import("resource://gre/modules/FileUtils.jsm");
-          let iniFactory = Components.classes['@mozilla.org/xpcom/ini-processor-factory;1']
-                           .getService(Components.interfaces.nsIINIParserFactory);
-
-          let file = new FileUtils.File(arguments[0]);
-
-          let writer = iniFactory.createINIParser(file)
-                       .QueryInterface(Components.interfaces.nsIINIParserWriter);
-
-          writer.setString(arguments[1], arguments[2], arguments[3]);
-          writer.writeFile(null, Components.interfaces.nsIINIParserWriter.WRITE_UTF16);
-        """, script_args=[self.config_file_path, self.INI_SECTION, self.INI_OPTION, new_channels])
+        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 channels to the update-settings.ini file.
+        """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 channels from the update-settings.ini file.
+        """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."""