run: Parse and quote correctly the command

Fix: #1478
This commit is contained in:
Mr. Outis
2019-01-09 00:01:55 -06:00
parent bad2ef5f0a
commit 7be42c8402
2 changed files with 38 additions and 34 deletions
+23 -17
View File
@@ -4,26 +4,11 @@ from dvc.exceptions import DvcException
class CmdRun(CmdBase):
def _joined_cmd(self):
if len(self.args.command) == 0:
return ''
if len(self.args.command) == 1:
return self.args.command[0]
cmd = ''
for chunk in self.args.command:
if len(chunk.split()) != 1:
fmt = ' "{}"'
else:
fmt = ' {}'
cmd += fmt.format(chunk)
return cmd
def run(self):
overwrite = (self.args.yes or self.args.overwrite_dvcfile)
try:
self.project.run(cmd=self._joined_cmd(),
self.project.run(cmd=self._parsed_cmd(),
outs=self.args.outs,
outs_no_cache=self.args.outs_no_cache,
metrics_no_cache=self.args.metrics_no_cache,
@@ -39,3 +24,24 @@ class CmdRun(CmdBase):
return 1
return 0
def _parsed_cmd(self):
"""
We need to take into account two cases:
- ['python code.py foo bar']: Used mainly with dvc as a library
- ['echo', 'foo bar']: List of arguments received from the CLI
The second case would need quoting, as it was passed through:
dvc run echo "foo bar"
"""
if len(self.args.command) < 2:
return ' '.join(self.args.command)
return ' '.join(self._quote_argument(arg) for arg in self.args.command)
def _quote_argument(self, argument):
if ' ' not in argument or '"' in argument:
return argument
return '"{}"'.format(argument)
+15 -17
View File
@@ -7,6 +7,7 @@ import subprocess
from dvc.main import main
from dvc.utils import file_md5
from dvc.system import System
from dvc.stage import Stage
from dvc.stage import StageFileBadNameError, MissingDep
from dvc.stage import StageBadCwdError, StageFileAlreadyExistsError
from dvc.exceptions import (OutputDuplicationError,
@@ -173,33 +174,30 @@ class TestCmdRun(TestDvc):
'-o', 'out',
'-f', 'out.dvc',
'python', self.CODE, self.FOO, 'out'])
stage = Stage.load(self.dvc, fname='out.dvc')
self.assertEqual(ret, 0)
self.assertTrue(os.path.isfile('out'))
self.assertTrue(os.path.isfile('out.dvc'))
self.assertTrue(filecmp.cmp(self.FOO, 'out', shallow=False))
self.assertEqual(stage.cmd, 'python code.py foo out')
def test_run_args_from_cli(self):
ret = main(['run', 'echo', 'foo'])
stage = Stage.load(self.dvc, fname='Dvcfile')
self.assertEqual(ret, 0)
self.assertEqual(stage.cmd, 'echo foo')
def test_run_bad_command(self):
ret = main(['run',
'non-existing-command'])
ret = main(['run', 'non-existing-command'])
self.assertNotEqual(ret, 0)
def test_run_args_with_spaces(self):
with open(self.CODE, 'w') as fobj:
fobj.write(
"import sys\nopen(sys.argv[1], 'w+').write(sys.argv[2])")
arg = 'arg1 arg2'
log = 'log'
ret = main(['run',
'python',
self.CODE,
log,
'arg1 arg2'])
ret = main(['run', 'echo', 'foo bar'])
stage = Stage.load(self.dvc, fname='Dvcfile')
self.assertEqual(ret, 0)
with open(log, 'r') as fobj:
self.assertEqual(fobj.read(), arg)
self.assertEqual(stage.cmd, 'echo "foo bar"')
@mock.patch.object(subprocess, 'Popen', side_effect=KeyboardInterrupt)
def test_keyboard_interrupt(self, mock_popen):