diff --git a/NEWS b/NEWS index 4ff6eb53e27af8cc7ce312c0f18912dfbe9892aa..44d9f0c6a5b2268bd9608271700157d45d5ec346 100644 --- a/NEWS +++ b/NEWS @@ -59,6 +59,9 @@ New in spot 2.5.2.dev (not yet released) - "autfilt -B --sat-minimize" was incorrectly producing transition-based automata. + - Using spot.automata("cmd...|") to read just a few automata out of + an infinite stream would not properly terminate the command. + New in spot 2.5.2 (2018-03-25) Bugs fixed: diff --git a/python/spot/__init__.py b/python/spot/__init__.py index 6621d4767f7a68fb7ba12cc3bc1f127dd359862c..9531e644f7a9a16c1c5317473af7bca3d3ef15fa 100644 --- a/python/spot/__init__.py +++ b/python/spot/__init__.py @@ -34,6 +34,7 @@ import subprocess import os import signal import tempfile +from contextlib import suppress as _supress # The parrameters used by default when show() is called on an automaton. _show_default = None @@ -441,30 +442,33 @@ def automata(*sources, timeout=None, ignore_abort=True, else: p = automaton_stream_parser(filename, o) a = True - while a: - # This returns None when we reach the end of the file. - a = p.parse(_bdd_dict).aut - if a: - yield a + # Using proc as a context manager ensures that proc.stdout will be + # closed on exit, and the process will be properly waited for. + # This is important when running tools that produce an infinite + # stream of automata and that must be killed once the generator + # returned by spot.automata() is destroyed. Otherwise, _supress() + # is just a dummy context manager that does nothing (Python 3.7 + # introduces nullcontext() for this purpose, but at the time of + # writing we support Python 3.4). + mgr = proc if proc else _supress() + with mgr: + while a: + # This returns None when we reach the end of the file. + a = p.parse(_bdd_dict).aut + if a: + yield a finally: # Make sure we destroy the parser (p) and the subprocess - # (prop) in the correct order... + # (prop) in the correct order. del p if proc is not None: - if not a: - # We reached the end of the stream. Wait for the - # process to finish, so that we get its exit code. - ret = proc.wait() - else: - # if a != None, we probably got there through an - # exception, and the subprocess might still be - # running. Check if an exit status is available - # just in case. - ret = proc.poll() + ret = proc.returncode del proc - if ret: + # Do not complain about the exit code if we are already raising + # an exception. + if ret and sys.exc_info()[0] is None: raise subprocess.CalledProcessError(ret, filename[:-1]) - # deleting o explicitely now prevents Python 3.5 from + # deleting o explicitly now prevents Python 3.5 from # reporting the following error: " returned a result with # an error set". It's not clear to me if the bug is in Python diff --git a/tests/Makefile.am b/tests/Makefile.am index 68d171743f01958c6532d116ffb4269db6c2741e..8bb47e250f4dc805800cdf1aea24280f72e2b859 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -351,10 +351,10 @@ TESTS_ipython = \ # do not consider part of the documentation: those have to start # with a _. TESTS_python = \ + python/341.py \ python/_altscc.ipynb \ python/_autparserr.ipynb \ python/_aux.ipynb \ - python/_word.ipynb \ python/accparse2.py \ python/alarm.py \ python/alternating.py \ @@ -362,6 +362,7 @@ TESTS_python = \ python/bddnqueen.py \ python/bugdet.py \ python/declenv.py \ + python/_word.ipynb \ python/decompose_scc.py \ python/dualize.py \ python/except.py \ diff --git a/tests/python/341.py b/tests/python/341.py new file mode 100644 index 0000000000000000000000000000000000000000..6a0ddea414ea95b1e9aa866869c676e6526ada6b --- /dev/null +++ b/tests/python/341.py @@ -0,0 +1,35 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2017 Laboratoire de Recherche et Développement de l'Epita +# (LRDE). +# +# This file is part of Spot, a model checking library. +# +# Spot is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Spot is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +# License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import spot +from subprocess import _active + +def two_intersecting_automata(): + """return two random automata with a non-empty intersection""" + g = spot.automata('randaut -A4 -Q5 -n-1 2 |') + for a, b in zip(g, g): + if a.intersects(b): + return a, b + +for i in range(5): + two_intersecting_automata() + +n = len(_active) +print(n, "active processes") +assert(n == 0);