| 1 | # -*- coding: utf-8 -*-
|
|---|
| 2 | """Specialized interfaces for invoking modules for testing framework
|
|---|
| 3 |
|
|---|
| 4 | Copyright (C) 2014 by the GRASS Development Team
|
|---|
| 5 | This program is free software under the GNU General Public
|
|---|
| 6 | License (>=v2). Read the file COPYING that comes with GRASS GIS
|
|---|
| 7 | for details.
|
|---|
| 8 |
|
|---|
| 9 | :authors: Vaclav Petras, Soeren Gebbert
|
|---|
| 10 | """
|
|---|
| 11 |
|
|---|
| 12 | import subprocess
|
|---|
| 13 | from grass.script.core import start_command
|
|---|
| 14 | from grass.script.utils import encode, decode
|
|---|
| 15 | from grass.exceptions import CalledModuleError
|
|---|
| 16 | from grass.pygrass.modules import Module
|
|---|
| 17 |
|
|---|
| 18 | from .utils import do_doctest_gettext_workaround
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 | class SimpleModule(Module):
|
|---|
| 22 | """Simple wrapper around pygrass.modules.Module to make sure that
|
|---|
| 23 | run\_, finish\_, stdout and stderr are set correctly.
|
|---|
| 24 |
|
|---|
| 25 | >>> mapcalc = SimpleModule('r.mapcalc', expression='test_a = 1',
|
|---|
| 26 | ... overwrite=True)
|
|---|
| 27 | >>> mapcalc.run()
|
|---|
| 28 | Module('r.mapcalc')
|
|---|
| 29 | >>> mapcalc.popen.returncode
|
|---|
| 30 | 0
|
|---|
| 31 |
|
|---|
| 32 | >>> colors = SimpleModule('r.colors',
|
|---|
| 33 | ... map='test_a', rules='-', stdin_='1 red')
|
|---|
| 34 | >>> colors.run()
|
|---|
| 35 | Module('r.colors')
|
|---|
| 36 | >>> colors.popen.returncode
|
|---|
| 37 | 0
|
|---|
| 38 | >>> str(colors.inputs.stdin)
|
|---|
| 39 | '1 red'
|
|---|
| 40 | >>> str(colors.outputs.stdout)
|
|---|
| 41 | ''
|
|---|
| 42 | >>> colors.outputs.stderr.strip()
|
|---|
| 43 | u"Color table for raster map <test_a> set to 'rules'"
|
|---|
| 44 | """
|
|---|
| 45 | def __init__(self, cmd, *args, **kargs):
|
|---|
| 46 | for banned in ['stdout_', 'stderr_', 'finish_', 'run_']:
|
|---|
| 47 | if banned in kargs:
|
|---|
| 48 | raise ValueError('Do not set %s parameter'
|
|---|
| 49 | ', it would be overriden' % banned)
|
|---|
| 50 | kargs['stdout_'] = subprocess.PIPE
|
|---|
| 51 | kargs['stderr_'] = subprocess.PIPE
|
|---|
| 52 | kargs['finish_'] = True
|
|---|
| 53 | kargs['run_'] = False
|
|---|
| 54 |
|
|---|
| 55 | Module.__init__(self, cmd, *args, **kargs)
|
|---|
| 56 |
|
|---|
| 57 |
|
|---|
| 58 | def call_module(module, stdin=None,
|
|---|
| 59 | merge_stderr=False, capture_stdout=True, capture_stderr=True,
|
|---|
| 60 | **kwargs):
|
|---|
| 61 | r"""Run module with parameters given in `kwargs` and return its output.
|
|---|
| 62 |
|
|---|
| 63 | >>> print call_module('g.region', flags='pg') # doctest: +ELLIPSIS
|
|---|
| 64 | projection=...
|
|---|
| 65 | zone=...
|
|---|
| 66 | n=...
|
|---|
| 67 | s=...
|
|---|
| 68 | w=...
|
|---|
| 69 | >>> call_module('m.proj', flags='i', input='-', stdin="50.0 41.5")
|
|---|
| 70 | '8642890.65|6965155.61|0.00\n'
|
|---|
| 71 | >>> call_module('g.region', aabbbccc='notexist') # doctest: +IGNORE_EXCEPTION_DETAIL
|
|---|
| 72 | Traceback (most recent call last):
|
|---|
| 73 | ...
|
|---|
| 74 | CalledModuleError: Module run g.region ... ended with error
|
|---|
| 75 |
|
|---|
| 76 | If `stdin` is not set and `kwargs` contains ``input`` with value set
|
|---|
| 77 | to ``-`` (dash), the function raises an error.
|
|---|
| 78 |
|
|---|
| 79 | Note that ``input`` nor ``output`` parameters are used by this
|
|---|
| 80 | function itself, these are usually module parameters which this
|
|---|
| 81 | function just passes to it. However, when ``input`` is in parameters
|
|---|
| 82 | the function checks if its values is correct considering value of
|
|---|
| 83 | ``stdin`` parameter.
|
|---|
| 84 |
|
|---|
| 85 | :param str module: module name
|
|---|
| 86 | :param stdin: string to be used as module standard input (stdin) or `None`
|
|---|
| 87 | :param merge_stderr: if the standard error output should be merged with stdout
|
|---|
| 88 | :param kwargs: module parameters
|
|---|
| 89 |
|
|---|
| 90 | :returns: module standard output (stdout) as string or None if apture_stdout is False
|
|---|
| 91 |
|
|---|
| 92 | :raises CalledModuleError: if module return code is non-zero
|
|---|
| 93 | :raises ValueError: if the parameters are not correct
|
|---|
| 94 |
|
|---|
| 95 | .. note::
|
|---|
| 96 | The data read is buffered in memory, so do not use this method
|
|---|
| 97 | if the data size is large or unlimited.
|
|---|
| 98 | """
|
|---|
| 99 | # TODO: remove this:
|
|---|
| 100 | do_doctest_gettext_workaround()
|
|---|
| 101 | # implementation inspired by subprocess.check_output() function
|
|---|
| 102 | if stdin:
|
|---|
| 103 | if 'input' in kwargs and kwargs['input'] != '-':
|
|---|
| 104 | raise ValueError(_("input='-' must be used when stdin is specified"))
|
|---|
| 105 | if stdin == subprocess.PIPE:
|
|---|
| 106 | raise ValueError(_("stdin must be string or buffer, not PIPE"))
|
|---|
| 107 | kwargs['stdin'] = subprocess.PIPE # to be able to send data to stdin
|
|---|
| 108 | elif 'input' in kwargs and kwargs['input'] == '-':
|
|---|
| 109 | raise ValueError(_("stdin must be used when input='-'"))
|
|---|
| 110 | if merge_stderr and not (capture_stdout and capture_stderr):
|
|---|
| 111 | raise ValueError(_("You cannot merge stdout and stderr and not capture them"))
|
|---|
| 112 | if 'stdout' in kwargs:
|
|---|
| 113 | raise TypeError(_("stdout argument not allowed, it could be overridden"))
|
|---|
| 114 | if 'stderr' in kwargs:
|
|---|
| 115 | raise TypeError(_("stderr argument not allowed, it could be overridden"))
|
|---|
| 116 |
|
|---|
| 117 | if capture_stdout:
|
|---|
| 118 | kwargs['stdout'] = subprocess.PIPE
|
|---|
| 119 | if capture_stderr:
|
|---|
| 120 | if merge_stderr:
|
|---|
| 121 | kwargs['stderr'] = subprocess.STDOUT
|
|---|
| 122 | else:
|
|---|
| 123 | kwargs['stderr'] = subprocess.PIPE
|
|---|
| 124 | process = start_command(module, **kwargs)
|
|---|
| 125 | # input=None means no stdin (our default)
|
|---|
| 126 | # for no stdout, output is None which is out interface
|
|---|
| 127 | # for stderr=STDOUT or no stderr, errors is None
|
|---|
| 128 | # which is fine for CalledModuleError
|
|---|
| 129 | output, errors = process.communicate(input=encode(decode(stdin)) if stdin else None)
|
|---|
| 130 | returncode = process.poll()
|
|---|
| 131 | if returncode:
|
|---|
| 132 | raise CalledModuleError(returncode, module, kwargs, errors)
|
|---|
| 133 | return decode(output) if output else None
|
|---|