More changes to support profile style of config.
authorHuang Hao <hao.h.huang@intel.com>
Tue, 11 Sep 2012 10:48:47 +0000 (18:48 +0800)
committerHuang Hao <hao.h.huang@intel.com>
Thu, 13 Sep 2012 01:58:16 +0000 (09:58 +0800)
- proflie section header should start with "proflie_", obs
    section header should start with "obs_", repo section
    header should start with "repo_".
- add base_prj and target_prj config to obs section
- generate profile style of config file in ~/.gbs.conf if no
    config file were found
- clean all parsers in function reset_from_conf()
- as described in issue#255, remove config file in cwd
- if current config is subcommand oriented style, convert it to
    profile oriented style into ~/.gbs.conf.template. Leave it
    original config file unchange.
- BrainConfigParser.set() is not compatible with super set, so
    change name to set_into_file()
- add class RepoConf and OBSConf to repo section info and obs
    section info, including section name which is useful for
    auto new style config generation.
- only update gbs.conf that we had changed and catch permission
    exception.
- add /etc/gbs.conf back.

Change-Id: I1dcee2814a7d5430ac3329a2c018e98b48734569

13 files changed:
gitbuildsys/cmd_build.py
gitbuildsys/cmd_remotebuild.py
gitbuildsys/conf.py
tests/test_config.py
tests/test_passwdx.py
tests/test_profile.py
tests/testdata/ini/home1.ini
tests/testdata/ini/local_repo.ini [deleted file]
tests/testdata/ini/no_such_profile_section_name.ini
tests/testdata/ini/profile.ini
tests/testdata/ini/profile_only_has_api.ini
tests/testdata/ini/project1.ini
tests/testdata/ini/subcommand.ini

index b090df4196ea90b84305bdc919203c7c4e998b88..440c537920e57ef2f1cd5c713ae8f1a7ea4076eb 100644 (file)
@@ -240,7 +240,7 @@ def do(opts, args):
         if opts.skip_conf_repos:
             repos = []
         else:
-            repos = configmgr.get_current_profile().get_repos()
+            repos = [i.url for i in configmgr.get_current_profile().repos]
 
         if opts.repositories:
             for i in opts.repositories:
index b87d3d311024b9060deaa022d5e9847fa427d532..06ef1b2bc262a52775f26f85c7adc2fef7821c9f 100644 (file)
@@ -65,10 +65,12 @@ def do(opts, args):
     else:
         msger.error('Invalid arguments, see gbs remotebuild -h for more info')
 
-    apiurl = configmgr.get_current_profile().get_api()
-    if not apiurl:
+    obsconf = configmgr.get_current_profile().obs
+    if not obsconf or not obsconf.url:
         msger.error('no obs api found, please add it to gbs conf and try again')
 
+    apiurl = obsconf.url
+
     if not apiurl.user:
         msger.error('empty user is not allowed for remotebuild, '
                     'please add user/passwd to gbs conf, and try again')
@@ -103,12 +105,12 @@ def do(opts, args):
     package = spec.name
 
     if opts.base_obsprj is None:
-        base_prj = configmgr.get('base_prj', 'remotebuild')
+        base_prj = obsconf.base or 'Tizen:Main'
     else:
         base_prj = opts.base_obsprj
 
     if opts.target_obsprj is None:
-        target_prj = configmgr.get('target_prj', 'remotebuild') or \
+        target_prj = obsconf.target or \
             "home:%s:gbs:%s" % (apiurl.user, base_prj)
     else:
         target_prj = opts.target_obsprj
index 018832af65ddad67d6bc56c8bb2f808ce2dba85f..0844853e72bc29681f41fafae7737c991ec43976 100644 (file)
@@ -21,7 +21,6 @@ Provides classes and functions to read and write gbs.conf.
 
 from __future__ import with_statement
 import os
-import re
 import ast
 import base64
 from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError, \
@@ -29,6 +28,7 @@ from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError, \
 
 from gitbuildsys import msger, errors
 from gitbuildsys.safe_url import SafeURL
+from gitbuildsys.utils import Temp
 
 
 def decode_passwdx(passwdx):
@@ -53,54 +53,11 @@ def split_and_evaluate_string(string, sep=None, maxsplit=-1):
     return [ evalute_string(i.strip()) for i in string.split(sep, maxsplit) ]
 
 
-class SectionPattern(object):
-    '''Pattern of section that support [section "name"] and [section].
-    1. If there is white-space in section header, it must obey the format like:
-        section_type white_spaces section_name,
-    section_name could be any string.
-    2. otherwise section name is the whole string in brackets
-    '''
-
-    SECTCRE = re.compile(
-        r'\['                            # [
-        r'(?P<header>[^] \t]+)'          # section name without any white-space
-            r'([ \t]+'                   # or
-            r'(?P<name>[^]]+)'           # section type and section name
-            r')?'                        # this section name is optional
-        r'\]'                            # ]
-        )
-
-    class MatchObject(object):
-        '''Match object for SectionPattern'''
-
-        def __init__(self, match):
-            self.match = match
-
-        def group(self, _group1):
-            '''return a tuple(type, name) if section has a name,
-            otherwise return a string as section name
-            '''
-            type_ = self.match.group('header')
-            name = self.match.group('name')
-            if not name:
-                return type_
-
-            name = evalute_string(name)
-            return type_, name
-
-    def match(self, string):
-        '''return MatchObject if string match the pattern'''
-        match = self.SECTCRE.match(string)
-        return self.MatchObject(match) if match else match
-
-
 class BrainConfigParser(SafeConfigParser):
     """Standard ConfigParser derived class which can reserve most of the
     comments, indents, and other user customized stuff inside the ini file.
     """
 
-    SECTCRE = SectionPattern()
-
     def read_one(self, filename):
         """only support one input file"""
         return SafeConfigParser.read(self, filename)
@@ -188,7 +145,7 @@ class BrainConfigParser(SafeConfigParser):
             else:
                 raise NoSectionError(section)
 
-    def set(self, section, option, value, replace_opt=None):
+    def set_into_file(self, section, option, value, replace_opt=None):
         """When set new value, need to update the readin file lines,
         which can be saved back to file later.
         """
@@ -213,11 +170,10 @@ class BrainConfigParser(SafeConfigParser):
         if self._fpname == '<???>':
             return
 
-        fptr = open(self._fpname, 'w')
-        for line in self._flines:
-            if line is not None:
-                fptr.write(line)
-        fptr.close()
+        with open(self._fpname, 'w') as fptr:
+            buf = ''.join([ line for line in self._flines if line is not None ])
+            fptr.write(buf)
+
 
 class ConfigMgr(object):
     '''Support multi-levels of gbs.conf. Use this class to get and set
@@ -234,8 +190,6 @@ class ConfigMgr(object):
                 'build_server': 'https://api.tizen.org',
                 'user':         '',
                 'passwd':       '',
-                'base_prj':     'Tizen:Main',
-                'target_prj':   ''
             },
             'build': {
                 'build_cmd':    '/usr/bin/build',
@@ -243,35 +197,32 @@ class ConfigMgr(object):
             },
     }
 
-    DEFAULT_CONF_TEMPLATE = """[general]
-; general settings
-tmpdir = $general__tmpdir
-editor = $general__editor
+    DEFAULT_CONF_TEMPLATE = '''[general]
+profile = profile.tizen
+tmpdir = /var/tmp
 
-[remotebuild]
-; settings for build subcommand
-build_server = $remotebuild__build_server
-user = $remotebuild__user
+[profile.tizen]
+; common authentication info for whole profile
+#user =
 ; CAUTION: please use the key name "passwd" to reset plaintext password
-passwdx = $remotebuild__passwdx
-; Default base project
-base_prj = $remotebuild__base_prj
-; Default target project
-target_prj = $remotebuild__target_prj
-
-[build]
-build_cmd = $build__build_cmd
-distconf = $build__distconf
-
-; optional, repos definitions
-#repo1.url=
-#repo1.user=
-#repo1.passwd=
-; one more repo
-#repo2.url=
-#repo2.user=
-#repo2.passwd=
-"""
+#passwd =
+obs = obs.tizen
+; comma separated list of repositories
+repos = repo.tizen_latest
+distconf = /usr/share/gbs/tizen-1.0.conf
+
+[obs.tizen]
+url = https://api.tizen.org
+; optinal user/passwd, set if differ from proflie's user/passwd
+#user =
+#passwd =
+
+[repo.tizen_latest]
+url = http://download.tizen.org/snapshots/trunk/common/latest/
+; optinal user/passwd, set if differ from proflie's user/passwd
+#user =
+#passwdx =
+'''
 
     # make the manager class as singleton
     _instance = None
@@ -296,9 +247,10 @@ distconf = $build__distconf
             # use the default path
             fpaths = self._lookfor_confs()
             if not fpaths:
-                if not self._new_conf():
-                    msger.error('No config file available')
+                self._new_conf()
+                fpaths = self._lookfor_confs()
 
+        self._cfgparsers = []
         for fpath in fpaths:
             cfgparser = BrainConfigParser()
             try:
@@ -311,70 +263,34 @@ distconf = $build__distconf
     @staticmethod
     def _lookfor_confs():
         """Look for available config files following the order:
-            > Current git
-            > Cwd
+            > Current project
             > User
+            > System
         """
 
         paths = []
-        for path in (os.path.abspath('.git/gbs.conf'),
-                     os.path.abspath('.gbs.conf'),
-                     os.path.expanduser('~/.gbs.conf')):
+        for path in (os.path.abspath('.gbs.conf'),
+                     os.path.expanduser('~/.gbs.conf'),
+                     '/etc/gbs.conf'):
             if os.path.exists(path) and path not in paths:
                 paths.append(path)
 
         return paths
 
-    def get_default_conf(self, defaults=None):
-        'returns ini template string contains default values'
-        from string import Template
-        if not defaults:
-            defaults = self.DEFAULTS
-
-        tmpl_keys = {}
-        for sec, opts in defaults.iteritems():
-            for opt, val in opts.iteritems():
-                tmpl_keys['%s__%s' % (sec, opt)] = val
-
-        return Template(self.DEFAULT_CONF_TEMPLATE).safe_substitute(tmpl_keys)
-
-    def _new_conf(self, fpath=None):
-        'generate a new conf file located by fpath'
-        if not fpath:
-            fpath = os.path.expanduser('~/.gbs.conf')
-
-        import getpass
-        msger.info('Creating config file %s ... ' % fpath)
-        # user and passwd in [build] section need user input
-        defaults = self.DEFAULTS.copy()
-        build_server = raw_input('Remote build server url (use %s by default):'\
-                                 % defaults['remotebuild']['build_server'])
-        if build_server:
-            defaults['remotebuild']['build_server'] = build_server
-
-        defaults['remotebuild']['user'] = \
-                          raw_input('Username for remote build server '\
-                    '(type <enter> to skip): ')
-
-        if defaults['remotebuild']['user']:
-            msger.info('Your password will be encoded before saving ...')
-            defaults['remotebuild']['passwdx'] = \
-                encode_passwd(getpass.getpass())
-        else:
-            defaults['remotebuild']['passwdx'] = \
-                encode_passwd(defaults['remotebuild']['passwd'])
+    def _new_conf(self):
+        'generate a default conf file in home dir'
+        fpath = os.path.expanduser('~/.gbs.conf')
 
         with open(fpath, 'w') as wfile:
-            wfile.write(self.get_default_conf(defaults))
+            wfile.write(self.DEFAULT_CONF_TEMPLATE)
         os.chmod(fpath, 0600)
 
-        msger.info('Done. Your gbs config is now located at %s' % fpath)
-        msger.warning("Don't forget to double-check the config manually.")
-        return True
+        msger.warning('Created a new config file %s. Please check and edit '
+            'your authentication information.' % fpath)
 
     def _check_passwd(self):
         'convert passwd item to passwdx and then update origin conf files'
-        replaced_keys = False
+        dirty = set()
 
         all_sections = set()
         for layer in self._cfgparsers:
@@ -390,16 +306,16 @@ distconf = $build__distconf
                             if plainpass is None:
                                 # empty string password is acceptable here
                                 continue
-                            cfgparser.set(sec,
+                            cfgparser.set_into_file(sec,
                                      key + 'x',
                                      encode_passwd(plainpass),
                                      key)
-                            replaced_keys = True
+                            dirty.add(cfgparser)
 
-        if replaced_keys:
+        if dirty:
             msger.warning('plaintext password in config files will '
                           'be replaced by encoded ones')
-            self.update()
+            self.update(dirty)
 
     def _get(self, opt, section='general'):
         'get value from multi-levels of config file'
@@ -452,45 +368,121 @@ distconf = $build__distconf
         else:
             return self._get(opt, section)
 
-    def update(self):
+    def update(self, cfgparsers):
         'update changed values into files on disk'
-        for cfgparser in self._cfgparsers:
-            cfgparser.update()
+        for cfgparser in cfgparsers:
+            try:
+                cfgparser.update()
+            except IOError, err:
+                msger.warning('update config file error: %s' % err)
+
+
+class OBSConf(object):
+    'Config items related to obs section'
+
+    def __init__(self, parent, name, url, base, target):
+        self.parent = parent
+        self.name = name
+        self.url = url
+        self.base = base
+        self.target = target
+
+    def dump(self, fhandler):
+        'dump ini to file object'
+        parser = BrainConfigParser()
+        parser.add_section(self.name)
+
+        parser.set(self.name, 'url', self.url)
+
+        if self.url.user and self.url.user != self.parent.common_user:
+            parser.set(self.name, 'user', self.url.user)
+
+        if self.url.passwd and self.url.passwd != self.parent.common_password:
+            parser.set(self.name, 'passwdx',
+                       encode_passwd(self.url.passwd))
+
+        if self.base:
+            parser.set(self.name, 'base_prj', self.base)
+
+        if self.target:
+            parser.set(self.name, 'target_prj', self.target)
+        parser.write(fhandler)
+
+
+class RepoConf(object):
+    'Config items related to repo section'
+
+    def __init__(self, parent, name, url):
+        self.parent = parent
+        self.name = name
+        self.url = url
+
+    def dump(self, fhandler):
+        'dump ini to file object'
+        parser = BrainConfigParser()
+        parser.add_section(self.name)
+
+        parser.set(self.name, 'url', self.url)
+
+        if self.url.user and self.url.user != self.parent.common_user:
+            parser.set(self.name, 'user', self.url.user)
+
+        if self.url.passwd and self.url.passwd != self.parent.common_password:
+            parser.set(self.name, 'passwdx',
+                       encode_passwd(self.url.passwd))
+        parser.write(fhandler)
 
 
 class Profile(object):
     '''Profile which contains all config values related to same domain'''
 
-    def __init__(self, user, password):
+    def __init__(self, name, user, password):
+        self.name = name
         self.common_user = user
         self.common_password = password
         self.repos = []
-        self.api = None
+        self.obs = None
 
-    def make_url(self, url, user, password):
-        '''make a safe url which contains auth info'''
-        user = user or self.common_user
-        password = password or self.common_password
-        try:
-            return SafeURL(url, user, password)
-        except ValueError, err:
-            raise errors.ConfigError('%s for %s' % (str(err), url))
+    def _update_url(self, url):
+        'update url by common auth info'
+        if not url.user:
+            url.user = self.common_user
+        if not url.passwd:
+            url.passwd = self.common_password
+        return url
 
-    def add_repo(self, url, user, password):
+    def add_repo(self, repoconf):
         '''add a repo to repo list of the profile'''
-        self.repos.append(self.make_url(url, user, password))
+        self._update_url(repoconf.url)
+        self.repos.append(repoconf)
 
-    def set_api(self, url, user, password):
+    def set_obs(self, obsconf):
         '''set OBS api of the profile'''
-        self.api = self.make_url(url, user, password)
+        self._update_url(obsconf.url)
+        self.obs = obsconf
+
+    def dump(self, fhandler):
+        'dump ini to file object'
+        parser = BrainConfigParser()
+        parser.add_section(self.name)
 
-    def get_repos(self):
-        '''get repo list of the profile'''
-        return self.repos
+        if self.common_user:
+            parser.set(self.name, 'user', self.common_user)
+        if self.common_password:
+            parser.set(self.name, 'passwdx',
+                       encode_passwd(self.common_password))
 
-    def get_api(self):
-        '''get OBS api of the profile'''
-        return self.api
+        if self.obs:
+            parser.set(self.name, 'obs', self.obs.name)
+            self.obs.dump(fhandler)
+
+        if self.repos:
+            names = []
+            for repo in self.repos:
+                names.append(repo.name)
+                repo.dump(fhandler)
+            parser.set(self.name, 'repos', ', '.join(names))
+        parser.write(fhandler)
 
 
 class BizConfigManager(ConfigMgr):
@@ -506,9 +498,37 @@ class BizConfigManager(ConfigMgr):
         if self.is_profile_oriented():
             return self._build_profile_by_name(self.get('profile'))
 
-        msger.warning('subcommand oriented style of config is deprecated, '
-                      'please convert to profile oriented style.')
-        return self._build_profile_by_subcommand()
+        profile = self._build_profile_by_subcommand()
+        self.convert_to_new_style(profile)
+        return profile
+
+    def convert_to_new_style(self, profile):
+        'convert ~/.gbs.conf to new style'
+        def dump_general(fhandler):
+            'dump options in general section'
+            parser = BrainConfigParser()
+            parser.add_section('general')
+            parser.set('general', 'profile', profile.name)
+
+            for opt in self.options('general'):
+                val = self.get(opt)
+                if val != self.DEFAULTS['general'].get(opt):
+                    parser.set('general', opt, val)
+            parser.write(fhandler)
+
+        fname = '~/.gbs.conf.template'
+        try:
+            tmp = Temp()
+            with open(tmp.path, 'w') as fhandler:
+                dump_general(fhandler)
+                profile.dump(fhandler)
+            os.rename(tmp.path, os.path.expanduser(fname))
+        except IOError, err:
+            raise errors.ConfigError(err)
+
+        msger.warning('subcommand oriented style of config is deprecated. '
+            'Please check %s, a new profile oriented style of config which'
+            ' was converted from your current settings.' % fname)
 
     def get_optional_item(self, section, option, default=None):
         '''return default if section.option does not exist'''
@@ -517,33 +537,51 @@ class BizConfigManager(ConfigMgr):
         except errors.ConfigError:
             return default
 
-    def _get_url_section(self, section_id):
+    def _get_url_options(self, section_id):
         '''get url/user/passwd from a section'''
         url = self.get('url', section_id)
         user = self.get_optional_item(section_id, 'user')
         password = self.get_optional_item(section_id, 'passwd')
-        return url, user, password
+        try:
+            return SafeURL(url, user, password)
+        except ValueError, err:
+            raise errors.ConfigError('%s for %s' % (str(err), url))
 
     def _build_profile_by_name(self, name):
         '''return profile object by a given section'''
-        profile_id = ('profile', name)
-        user = self.get_optional_item(profile_id, 'user')
-        password = self.get_optional_item(profile_id, 'passwd')
-
-        profile = Profile(user, password)
-
-        conf_api = self.get_optional_item(profile_id, 'api')
-        if conf_api:
-            api = self.get('api', profile_id)
-            api_id = ('obs', api)
-            profile.set_api(*self._get_url_section(api_id))
-
-        conf_repos = self.get_optional_item(profile_id, 'repos')
-        if conf_repos:
-            repos = split_and_evaluate_string(conf_repos, ',')
-            for repo in repos:
-                repo_id = ('repo', repo)
-                profile.add_repo(*self._get_url_section(repo_id))
+        if not name.startswith('profile.'):
+            raise msger.error('profile section name must start '
+                              'with "profile.": %s' % name)
+
+        user = self.get_optional_item(name, 'user')
+        password = self.get_optional_item(name, 'passwd')
+
+        profile = Profile(name, user, password)
+
+        obs = self.get_optional_item(name, 'obs')
+        if obs:
+            if not obs.startswith('obs.'):
+                msger.error('obs section name should start '
+                            'with "obs.": %s' % obs)
+
+            obsconf = OBSConf(profile, obs,
+                              self._get_url_options(obs),
+                              self.get_optional_item(obs, 'base_prj'),
+                              self.get_optional_item(obs, 'target_prj'))
+            profile.set_obs(obsconf)
+
+        repos = self.get_optional_item(name, 'repos')
+        if repos:
+            for repo in repos.split(','):
+                repo = repo.strip()
+                if not repo.startswith('repo.'):
+                    msger.warning('repo section name should start '
+                                  'with "repo.": %s' % repo)
+                    continue
+
+                repoconf = RepoConf(profile, repo,
+                                    self._get_url_options(repo))
+                profile.add_repo(repoconf)
 
         return profile
 
@@ -585,19 +623,34 @@ class BizConfigManager(ConfigMgr):
 
     def _build_profile_by_subcommand(self):
         '''return profile object from subcommand oriented style of config'''
-        profile = Profile(None, None)
+        profile = Profile('profile.current', None, None)
 
-        section_id = 'remotebuild'
-        url = self.get('build_server', section_id)
-        user = self.get_optional_item(section_id, 'user')
-        password = self.get_optional_item(section_id, 'passwd')
-        profile.set_api(url, user, password)
+        sec = 'remotebuild'
+        addr = self.get('build_server', sec)
+        user = self.get_optional_item(sec, 'user')
+        password = self.get_optional_item(sec, 'passwd')
+
+        try:
+            url = SafeURL(addr, user, password)
+        except ValueError, err:
+            raise errors.ConfigError('%s for %s' % (str(err), addr))
+
+        obsconf = OBSConf(profile, 'obs.%s' % sec, url,
+                          self.get_optional_item('remotebuild', 'base_prj'),
+                          self.get_optional_item('remotebuild', 'target_prj'))
+        profile.set_obs(obsconf)
 
         repos = self._parse_build_repos()
         for key, item in repos:
             if 'url' not in item:
-                raise errors.ConfigError("Url is not specified for %s" % key)
-            profile.add_repo(item['url'], item.get('user'), item.get('passwd'))
+                raise errors.ConfigError("URL is not specified for %s" % key)
+            try:
+                url = SafeURL(item['url'], item.get('user'), item.get('passwd'))
+            except ValueError, err:
+                raise errors.ConfigError('%s for %s' % (str(err), item['url']))
+
+            repoconf = RepoConf(profile, 'repo.%s' % key, url)
+            profile.add_repo(repoconf)
 
         return profile
 
index 624a25e7defa97daa9d57f8297cda56f32d3aa25..61441983843cd78a081089b66190041f03dfdf85 100644 (file)
@@ -119,33 +119,6 @@ class ConfigGettingTest(unittest.TestCase):
         '''value can be overwrite if name is the same'''
         self.assertEqual('projv1', self.get('section', 'common_key'))
 
-    @Fixture(project='project1.ini')
-    def test_get_named_section(self):
-        '''get value from named section'''
-        self.assertEquals('projv4', self.get(('profile', 'rsa'), 'proj_only'))
-
-    @Fixture(home='home1.ini', project='project1.ini')
-    def test_inherit_named_section(self):
-        '''value can be inherit from named section correctly'''
-        self.assertEquals('homev4', self.get(('profile', 'rsa'), 'home_only'))
-
-    @Fixture(home='home1.ini', project='project1.ini')
-    def test_overwrite_named_section(self):
-        '''value can be overwrite from named section correctly'''
-        self.assertEquals('projv3', self.get(('profile', 'rsa'), 'common'))
-
-    @Fixture(project='project1.ini')
-    def test_no_such_named_section(self):
-        '''test no such section'''
-        self.assertRaises(ConfigError,
-                          self.get, ('profile', 'NOT_EXISTS'), 'key')
-
-    @Fixture(project='project1.ini')
-    def test_no_such_option_in_named_section(self):
-        '''test no such section'''
-        self.assertRaises(ConfigError,
-                          self.get, ('profile', 'rsa'), 'not_exists_option')
-
     @Fixture(home='home1.ini')
     def test_default_value(self):
         'test get hardcode default value '
index 8b4474b8df6d3d00170e24365227b380408d113f..152f8641329f1c607522f07fabbff476e3ec89c9 100644 (file)
@@ -21,10 +21,12 @@ from StringIO import StringIO
 
 from mock import patch
 
-from test_config import Fixture
 import gitbuildsys.conf
+from gitbuildsys.conf import BrainConfigParser
 from gitbuildsys.errors import ConfigError
 
+from test_config import Fixture
+
 
 class FakeFile(object):
     'Fake file used to get updated config file'
@@ -39,6 +41,13 @@ class FakeFile(object):
     def close(self):
         'do not close buffer, then call getvalue() to retrieve the content'
 
+    def __exit__(self, *_args):
+        'mock with statement'
+
+    def __enter__(self):
+        'mock with statement'
+        return self
+
     def getvalue(self):
         'get content of fake file'
         return self.buffer.getvalue()
@@ -70,8 +79,12 @@ repo1.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
     def test_two_files(self, fake_open):
         'test passwdx set back to two files'
         confs = [FakeFile(), FakeFile()]
-        bak = confs[:]
-        fake_open.side_effect = lambda *args, **kw: bak.pop()
+        def side_effect(name, _mode):
+            'fake open'
+            if name == '~/.gbs.conf':
+                return confs[0]
+            return confs[1]
+        fake_open.side_effect = side_effect
 
         reload(gitbuildsys.conf)
 
@@ -105,8 +118,10 @@ repo1.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
         self.assertEquals('secret', pwd)
 
     @Fixture(home='plain_passwd.ini')
-    def test_get_passwd(self, _fake_open):
+    def test_get_passwd(self, fake_open):
         'test get decode passwd'
+        fake_open.return_value = FakeFile()
+
         reload(gitbuildsys.conf)
 
         pwd = gitbuildsys.conf.configmgr.get('passwd', 'remotebuild')
@@ -129,5 +144,30 @@ repo1.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
         self.assertEquals('', pwd)
 
 
+@patch('gitbuildsys.conf.os.chmod')
+@patch('gitbuildsys.conf.open', create=True)
+class AutoGenerateTest(unittest.TestCase):
+    'test auto generation if no conf was found'
+
+    @Fixture()
+    def test_auto_generate_conf(self, fake_open, _fake_chmod):
+        'test auto generate conf should contain obs and repos'
+        conf = FakeFile()
+        fake_open.return_value = conf
+
+        reload(gitbuildsys.conf)
+
+        parser = BrainConfigParser()
+        parser.readfp(StringIO(conf.getvalue()))
+
+        name = parser.get('general', 'profile')
+        obs = parser.get(name, 'obs')
+        repos = parser.get(name, 'repos')
+
+        self.assertTrue(parser.has_section(obs))
+        for repo in repos.split(','):
+            self.assertTrue(parser.has_section(repo.strip()))
+
+
 if __name__ == '__main__':
     unittest.main()
\ No newline at end of file
index b53cb3835405f31795af37476a4b22e08ddf442b..4efef9ad6f829c171a115a631ce84fad6964aa78 100644 (file)
 """Functional tests for profile style of config"""
 import unittest
 
+from mock import patch, MagicMock, Mock
+
 import gitbuildsys.conf
 from test_config import Fixture
+from test_passwdx import FakeFile
 
 
 def get_profile():
@@ -34,19 +37,19 @@ class ProfileStyleTest(unittest.TestCase):
     @Fixture(home='profile.ini')
     def test_profile_api(self):
         'test get obs api'
-        self.assertEquals('https://api.tz/path', get_profile().get_api())
+        self.assertEquals('https://api.tz/path', get_profile().obs.url)
 
     @Fixture(home='profile.ini')
     def test_api_inherit_auth(self):
         'test api can inherit auto from parent profile section'
         self.assertEquals('https://Alice:secret@api.tz/path',
-                          get_profile().get_api().full)
+                          get_profile().obs.url.full)
 
     @Fixture(home='profile_only_has_api.ini')
     def test_api_auth_can_be_overwrite(self):
         'test api auth can be overwrite'
         self.assertEquals('https://Bob:classified@api.tz/path',
-                          get_profile().get_api().full)
+                          get_profile().obs.url.full)
 
     @Fixture(home='profile.ini')
     def test_profile_repos_in_order(self):
@@ -55,46 +58,58 @@ class ProfileStyleTest(unittest.TestCase):
                            'https://repo/ia32/non-oss',
                            'https://repo/ia32/base',
                            '/local/path'],
-                          get_profile().get_repos())
+                          [i.url for i in get_profile().repos])
 
     @Fixture(home='profile.ini')
     def test_repo_inherit_auth(self):
         'test repo can inherit auth from parent section'
         self.assertEquals('https://Alice:secret@repo/ia32/main',
-                          get_profile().get_repos()[0].full)
+                          get_profile().repos[0].url.full)
 
     @Fixture(home='profile.ini')
     def test_repo_overwrite_auth(self):
         'test repo auth can be overwrite'
         self.assertEquals('https://Bob:classified@repo/ia32/base',
-                          get_profile().get_repos()[2].full)
+                          get_profile().repos[2].url.full)
 
     @Fixture(home='no_such_profile_section_name.ini')
     def test_no_such_profile(self):
         'test get a empty profile when name does not exist'
         profile = get_profile()
-        self.assertEquals(None, profile.get_api())
-        self.assertEquals([], profile.get_repos())
+        self.assertEquals(None, profile.obs)
+        self.assertEquals([], profile.repos)
 
     @Fixture(home='profile.ini')
     def test_local_repo_need_not_auth(self):
         '''test local path needn't auth info'''
-        self.assertEquals('/local/path', get_profile().get_repos()[3].full)
+        self.assertEquals('/local/path', get_profile().repos[3].url.full)
+
+    @Fixture(home='profile.ini')
+    def test_obs_base_project(self):
+        'test read base project from conf'
+        self.assertEquals('base', get_profile().obs.base)
+
+    @Fixture(home='profile.ini')
+    def test_obs_target_project(self):
+        'test read target project from conf'
+        self.assertEquals('target', get_profile().obs.target)
 
 
+@patch('gitbuildsys.conf.open', MagicMock(), create=True)
+@patch('gitbuildsys.conf.os.rename', Mock())
 class SubcommandStyleTest(unittest.TestCase):
     '''test for subcommand oriented config'''
 
     @Fixture(home='subcommand.ini')
     def test_api(self):
         'test obs api'
-        self.assertEquals('https://api/build/server', get_profile().get_api())
+        self.assertEquals('https://api/build/server', get_profile().obs.url)
 
     @Fixture(home='subcommand.ini')
     def test_api_auth(self):
         'test api auth'
         self.assertEquals('https://Alice:secret@api/build/server',
-                          get_profile().get_api().full)
+                          get_profile().obs.url.full)
 
     @Fixture(home='subcommand.ini')
     def test_repos_in_order(self):
@@ -102,13 +117,59 @@ class SubcommandStyleTest(unittest.TestCase):
         self.assertEquals(['https://repo1/path',
                            'https://repo2/path',
                            '/local/path/repo'],
-                          get_profile().get_repos())
+                          [i.url for i in get_profile().repos])
 
     @Fixture(home='subcommand.ini')
     def test_repo_auth(self):
         'test repo auth'
         self.assertEquals('https://Alice:secret@repo1/path',
-                          get_profile().get_repos()[0].full)
+                          get_profile().repos[0].url.full)
+
+
+
+@patch('gitbuildsys.conf.open', create=True)
+class ConvertTest(unittest.TestCase):
+    'Test convert subcommand to profile'
+
+    @Fixture(home='subcommand.ini')
+    def test_convert(self, fake_open):
+        'test convert'
+        conf = FakeFile()
+        fake_open.return_value = conf
+
+        get_profile()
+
+        self.assertEquals(conf.getvalue(), '''[general]
+profile = profile.current
+
+[obs.remotebuild]
+url = https://api/build/server
+user = Alice
+passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+base_prj = Main
+target_prj = Target
+
+[repo.repo1]
+url = https://repo1/path
+user = Alice
+passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+
+[repo.repo2]
+url = https://repo2/path
+user = Alice
+passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+
+[repo.repo3]
+url = /local/path/repo
+
+[profile.current]
+obs = obs.remotebuild
+repos = repo.repo1, repo.repo2, repo.repo3
+
+''')
+
+
+
 
 
 if __name__ == '__main__':
index 39daae61a7849537f9e365961e75c5a37a1d0367..6a996f57f2168a19a0f402a5215189ee634ecd33 100644 (file)
@@ -2,6 +2,6 @@
 common_key = homev1
 home_only_key = homev2
 
-[profile "rsa"]
+[profile.rsa]
 common = homev3
 home_only = homev4
diff --git a/tests/testdata/ini/local_repo.ini b/tests/testdata/ini/local_repo.ini
deleted file mode 100644 (file)
index ffed290..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-[build]
-repo1.url = https://repo1/path
-repo1.user = Alice
-repo1.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
-
-repo2.url = https://repo2/path
-repo2.user = Alice
-repo2.passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
index a51818e82f46e22b726afd5dd85f114419060c04..d1622eec43eba48caf4c6b3254a012514c402194 100644 (file)
@@ -1,2 +1,2 @@
 [general]
-profile = test
\ No newline at end of file
+profile = profile.test
\ No newline at end of file
index 54b8284eccdc101dc608b00505cd2e56d5e5ef57..a646dc5a2374711fc4e8f1f935481d9b1f50682e 100644 (file)
@@ -1,27 +1,29 @@
 [general]
-profile = tz
+profile = profile.tz
 
-[profile "tz"]
+[profile.tz]
 user = Alice
 #passwd = secret
 passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
-repos = ia32_main, ia32_non-oss, ia32_base, local
-api = tz
+repos = repo.ia32_main, repo.ia32_non-oss, repo.ia32_base, repo.local
+obs = obs.tz
 
-[obs "tz"]
+[obs.tz]
 url = https://api.tz/path
+base_prj = base
+target_prj = target
 
-[repo ia32_main]
+[repo.ia32_main]
 url = https://repo/ia32/main
 
-[repo ia32_non-oss]
+[repo.ia32_non-oss]
 url = https://repo/ia32/non-oss
 
-[repo ia32_base]
+[repo.ia32_base]
 url = https://repo/ia32/base
 user = Bob
 #passwd = classified
 passwdx = QlpoOTFBWSZTWRwZil4AAACBgC8kCAAgADEMCCAPKGaQLT4u5IpwoSA4MxS8
 
-[repo local]
+[repo.local]
 url = /local/path
\ No newline at end of file
index 89035f01704f8f7f2ded6cc5d3335d472032adbd..5d3f6c97c06c1963dc0492bb43372a7a2fc08501 100644 (file)
@@ -1,13 +1,13 @@
 [general]
-profile = test
+profile = profile.test
 
-[profile "test"]
+[profile.test]
 user = Alice
 #passwd = secret
 passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
-api = test_api
+obs = obs.test_api
 
-[obs "test_api"]
+[obs.test_api]
 url = https://api.tz/path
 user = Bob
 #passwd = classified
index 20eeb19d9fb59ca16cf1f97ccf6801f96b2895fb..aec2410fcefb8dd71fb0503dcb182b57f85873b4 100644 (file)
@@ -2,6 +2,6 @@
 common_key = projv1
 proj_only_key = projv2
 
-[profile "rsa"]
+[profile.rsa]
 common = projv3
 proj_only = projv4
index 974abb390457f965351bde37870d79f104694a5d..ea0a9bb0a31c326a779dfe1093df83b90b737ca5 100644 (file)
@@ -3,6 +3,8 @@ build_server = https://api/build/server
 user = Alice
 #passwd = secret
 passwdx = QlpoOTFBWSZTWYfNdxYAAAIBgAoAHAAgADDNAMNEA24u5IpwoSEPmu4s
+base_prj = Main
+target_prj = Target
 
 [build]
 repo1.url = https://repo1/path