[MERGE] run_bzr_subprocess(env_changes={})

Martin Pool mbp at canonical.com
Wed Aug 30 04:20:32 BST 2006


On 28 Aug 2006, John Arbash Meinel <john at arbash-meinel.com> wrote:

> > That sounds good.  In general setting them while the process is
> > running is not a good idea.  (For example some systems scan $TZ at
> > libc startup and some every time it's needed.)  So
> > 
> >   run_bzr_subprocess(...,
> >       env_changes = {"REMOVE_ME": None, "LANG": "de_DE"})
> > 
> 
> I went ahead and merged the locale changes, since they had been
> approved, but I liked the idea of this, so I went ahead and updated
> run_bzr_subprocess, the locale tests, and added a direct test for this
> that the environment changes are used, but don't stick in the parent.

Great

> === modified file bzrlib/tests/__init__.py
> --- bzrlib/tests/__init__.py
> +++ bzrlib/tests/__init__.py
> @@ -843,12 +843,28 @@
>          profiled or debugged so easily.
>  
>          :param retcode: The status code that is expected.  Defaults to 0.  If
> -        None is supplied, the status code is not checked.
> +            None is supplied, the status code is not checked.
> +        :param env_changes: A dictionary which lists changes to environment
> +            variables. A value of None will unset the env variable.
> +            The values must be strings. The change will only occur in the
> +            child, so you don't need to fix the environment after running.
>          """
> +        env_changes = kwargs.get('env_changes', None)
> +        preexec_func = None
> +        if env_changes:
> +            def cleanup_environment():
> +                for env_var, value in env_changes.iteritems():
> +                    if value is None:
> +                        del os.environ[env_var]
> +                    else:
> +                        os.environ[env_var] = value
> +            preexec_func = cleanup_environment
> +
>          bzr_path = os.path.dirname(os.path.dirname(bzrlib.__file__))+'/bzr'
>          args = list(args)
> -        process = Popen([sys.executable, bzr_path]+args, stdout=PIPE, 
> -                         stderr=PIPE)
> +        process = Popen([sys.executable, bzr_path]+args,
> +                         stdout=PIPE, stderr=PIPE,
> +                         preexec_fn=preexec_func)
>          out = process.stdout.read()
>          err = process.stderr.read()
>          retcode = process.wait()

Wouldn't it be simpler to just run the preexec function every time?
It'll do nothing if there are no changes and that will avoid several
lines.

I recall that in some situations deleting from os.environ does not work.
Perhaps that was on old Pythons.  The manual says

## If the platform supports the unsetenv() function, you can delete items
## in this mapping to unset environment variables. unsetenv() will be
## called automatically when an item is deleted from os.environ.

Let's put it in and see if it fails anywhere.

>      def test_log_C(self):
> -        os.environ['LANG'] = 'C'
>          out, err = self.run_bzr_subprocess('--no-aliases', '--no-plugins',
>                                             '-q', 'log', '--log-format=long',
> -                                           'tree')
> +                                           'tree',
> +                                           env_changes={'LANG':'C',
> +                                                  'BZR_PROGRESS_BAR':'none'}
> +                                          )

I think when things start getting squished over like this it's better to
change indenting modes, like

    out, err = self.run_bzr_subprocess('--no-aliases', '--no-plugins',
        '-q', 'log', '--log-format=long', 'tree',
        env_changes={'LANG':'C', 'BZR_PROGRESS_BAR':'none'})

>  
> +    def test_run_bzr_subprocess_env(self):
> +        """run_bzr_subprocess can set environment variables in the child only.
> +
> +        These changes should not change the running process, only the child.
> +        """
> +        # The test suite should unset this variable
> +        self.assertEqual(None, os.environ.get('BZR_EMAIL'))
> +        out, err = self.run_bzr_subprocess('whoami', env_changes={
> +                                            'BZR_EMAIL':'Joe Foo <joe at foo.com>'
> +                                          })

Nice way to test it.

> +        self.assertEqual('', err)
> +        self.assertEqual('Joe Foo <joe at foo.com>\n', out)
> +        # And it should not be modified
> +        self.assertEqual(None, os.environ.get('BZR_EMAIL'))
> +
> +        # Do it again with a different address, just to make sure
> +        # it is actually changing
> +        out, err = self.run_bzr_subprocess('whoami', env_changes={
> +                                            'BZR_EMAIL':'Barry <bar at foo.com>'
> +                                          })
> +        self.assertEqual('', err)
> +        self.assertEqual('Barry <bar at foo.com>\n', out)
> +        self.assertEqual(None, os.environ.get('BZR_EMAIL'))

This doesn't test asking that a variable be deleted.  Perhaps you could
set it in the parent process, then run it in a subprocess set it to
None, then in a finally block remove it.

+1 with those changes.

-- 
Martin




More information about the bazaar mailing list