diff --git a/ChangeLog.rst b/ChangeLog.rst index 8ac8ccd..cb0a4a0 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,13 +1,19 @@ -ayrton (0.7.2) UNRELEASED; urgency=medium +ayrton (0.7.2.1) unstable; urgency=medium - * Fix running remote tests with other versions of python. - * Fix tests borken by a change in `ls`'s output. - * Fix iterating over the long output of a command à la `for line in foo(...): ...`. Currently you must add `_bg=True` to the execution options. - * Fix recognizing names bound by for loops. + * Fix iterating over the log ouput of a `Command` in synchronous mode (that is, not running in the `_bg`). This complements the fix in the previous release. + + -- Marcos Dione Fri, 26 Feb 2016 13:54:46 +0100 + +ayrton (0.7.2) unstable; urgency=medium + + * Fix running remote tests with other versions of Python. + * Fix tests broken by a change in `ls`'s output. + * Fix iterating over the long output of a `Command` à la `for line in foo(...): ...`. Currently you must add `_bg=True` to the execution options. + * Fix recognizing names bound by `for` loops. * Added options `-d|--debug`, `-dd|--debug2` and `-ddd|--debug3` for enabling debug logs. * Added option `-xxx|--trace-all` for tracing all python execution. Use with caution, it generates lots of output. - -- Marcos Dione Thu, 25 Feb 2016 12:40:04 +0100 + -- Marcos Dione Thu, 25 Feb 2016 13:09:08 +0100 ayrton (0.7.1) unstable; urgency=medium diff --git a/ayrton/__init__.py b/ayrton/__init__.py index e08c4b6..9458e5a 100644 --- a/ayrton/__init__.py +++ b/ayrton/__init__.py @@ -51,7 +51,7 @@ def debug (level=logging.DEBUG, filename='ayrton.log'): from ayrton.parser.astcompiler.astbuilder import ast_from_node from ayrton.ast_pprinter import pprint -__version__= '0.7.2' +__version__= '0.7.2.1' class ExecParams: diff --git a/ayrton/execute.py b/ayrton/execute.py index d65cc02..b32866c 100644 --- a/ayrton/execute.py +++ b/ayrton/execute.py @@ -115,6 +115,7 @@ class Command: supported_options= ('_in', '_out', '_err', '_end', '_chomp', '_encoding', '_env', '_bg', '_fails') + def __init__ (self, path): self.path= path self.exe= resolve_program (path) @@ -126,9 +127,11 @@ def __init__ (self, path): self._exit_code= None self.capture_file= None + self.captured_lines= None self.child_pid= None + def prepare_fds (self): if '_in' in self.options: i= self.options['_in'] @@ -204,6 +207,7 @@ def prepare_fds (self): logger.debug ("stderr_pipe: %s", self.stderr_pipe) + def child (self): logger.debug ('child') @@ -360,6 +364,7 @@ def prepare_args (self, cmd, args, kwargs): return ans + def prepare_arg (self, seq, name, value): if value!=False: if isiterable (value): @@ -410,6 +415,14 @@ def parent (self): # command to exit with a non-zero status, or zero if all commands exit # successfully. if not self.options['_bg']: + if self.options.get ('_out', None)==Capture: + # if we don't do this, a program with lots of output freezes + # when its output buffer is full + # on the other hand, this might take a lot of memory + # but it's the same as in foo=$(long_output) + self.prepare_capture_file () + self.captured_lines= self.capture_file.readlines () + self.wait () # NOTE: uhm? ayrton.runner.wait_for_pending_children () @@ -419,17 +432,19 @@ def parent (self): def wait (self): logger.debug (self.child_pid) - self._exit_code= os.waitpid (self.child_pid, 0)[1] >> 8 - if self._exit_code==127: - # NOTE: when running bash, it returns 127 when it can't find the script to run - raise CommandNotFound (self.path) + if self._exit_code is None: + self._exit_code= os.waitpid (self.child_pid, 0)[1] >> 8 + + if self._exit_code==127: + # NOTE: when running bash, it returns 127 when it can't find the script to run + raise CommandNotFound (self.path) - if (ayrton.runner.options.get ('errexit', False) and - self._exit_code!=0 and - not self.options.get ('_fails', False)): + if (ayrton.runner.options.get ('errexit', False) and + self._exit_code!=0 and + not self.options.get ('_fails', False)): - raise CommandFailed (self) + raise CommandFailed (self) def exit_code (self): @@ -460,6 +475,7 @@ def __call__ (self, *args, **kwargs): self.stdout_pipe= None self.stderr_pipe= None + # TODO: why this again here? see __init__() self._exit_code= None self.capture_file= None @@ -510,23 +526,31 @@ def prepare_capture_file (self): def __str__ (self): - if self._exit_code is None: - self.wait () + self.wait () - self.prepare_capture_file () + if self.captured_lines: + s= ''.join (self.captured_lines) + else: + self.prepare_capture_file () + s= self.capture_file.read () - return self.capture_file.read () + return s def __iter__ (self): logger.debug ('iterating!') - self.prepare_capture_file () + if self.captured_lines: + for line in self.captured_lines: + # while iterating we always remove the trailing \n + line= line.rstrip (os.linesep) + yield line + else: + self.prepare_capture_file () - if self.capture_file is not None: for line in self.capture_file.readlines (): - if self.options['_chomp']: - line= line.rstrip (os.linesep) + # while iterating we always remove the trailing \n + line= line.rstrip (os.linesep) logger.debug2 ('read line: %s', line) yield line @@ -534,36 +558,45 @@ def __iter__ (self): # finish him! logger.debug ('finished!') self.capture_file.close () - if self._exit_code is None: - self.wait () - else: - logger.debug ('dunno what to do!') + # if we're iterating, then the Command is in _bg + self.wait () + def readlines (self): - if self._exit_code is None: - self.wait () + self.wait () + + if self.captured_lines: + lines= self.captured_lines + else: + self.prepare_capture_file () + lines= self.capture_file.readlines () + self.capture_file.close () + + return lines - # ugly way to not leak the file() - return ( line for line in self ) # BUG this method is leaking an opened file() # self.capture_file def readline (self): - if self._exit_code is None: - self.wait () + self.wait () + + if self.captured_lines: + line= self.captured_lines.pop (0) + else: + self.prepare_capture_file () + line= self.capture_file.readline () - self.prepare_capture_file () - line= self.capture_file.readline () if self.options['_chomp']: line= line.rstrip (os.linesep) return line + def close (self): - if self._exit_code is None: - self.wait () + self.wait () self.capture_file.close () + def __del__ (self): # finish it if self._exit_code is None and self.child_pid is not None: diff --git a/ayrton/tests/scripts/testShortIter.ay b/ayrton/tests/scripts/testShortIter.ay index 5bbadf3..bb48d59 100644 --- a/ayrton/tests/scripts/testShortIter.ay +++ b/ayrton/tests/scripts/testShortIter.ay @@ -1,4 +1,4 @@ #! /usr/bin/env ayrton -for line in echo ('yes!'): +for line in echo ('yes!', _bg=True): ayrton_return_value= line.strip () diff --git a/ayrton/tests/test_ayrton.py b/ayrton/tests/test_ayrton.py index 45c0f33..5e84a73 100644 --- a/ayrton/tests/test_ayrton.py +++ b/ayrton/tests/test_ayrton.py @@ -385,17 +385,21 @@ def testComposing (self): def testBg (self): '''This test takes some time...''' self.doTest ('testBg.ay', 'yes!') + # the tests script does not wait for find to finish + # so we do it here self.runner.wait_for_pending_children () def testShortIter (self): '''This test takes some time...''' self.doTest ('testShortIter.ay', 'yes!') - self.runner.wait_for_pending_children () def testLongIter (self): '''This test takes some time...''' self.doTest ('testLongIter.ay', 'yes!') - self.runner.wait_for_pending_children () + + def testLongOutput (self): + '''This test takes some time...''' + self.doTest ('testLongOutput.ay', 'yes!') class CommandDetection (ScriptExecution): diff --git a/ayrton/tests/test_execute.py b/ayrton/tests/test_execute.py index 88a9f0c..effc595 100644 --- a/ayrton/tests/test_execute.py +++ b/ayrton/tests/test_execute.py @@ -194,7 +194,7 @@ def testOutAsIterable (self): text= '_out=Capture' a= echo (text, _out=Capture) for i in a: - # notice that here there's no \n + # NOTE: that here there's no \n! self.assertEqual (i, text) def testOutAsFd (self): diff --git a/release.ay b/release.ay index bea7f74..9a5b7c2 100755 --- a/release.ay +++ b/release.ay @@ -5,6 +5,12 @@ import ayrton option ('-e') if make ('tests'): + # with remote ('mustang'): + # cd (bash ('~/src/projects/ayrton')) + # git ('pull', 'devel') + # # if this fails, hopefully the whole script stops :) + # make ('tests') + # this command might fail if it wants to uncommited= git ('status', --short=True) | grep ('^ M', _out=Capture, _fails=True) if uncommited: