helpers.py revision 11828:36b064696175
16019SN/A#!/usr/bin/env python2
26019SN/A#
312542Sgiacomo.travaglini@arm.com# Copyright (c) 2016 ARM Limited
47102SN/A# All rights reserved
57102SN/A#
67102SN/A# The license below extends only to copyright in the software and shall
77102SN/A# not be construed as granting a license to any other intellectual
87102SN/A# property including but not limited to intellectual property relating
97102SN/A# to a hardware implementation of the functionality of the software
107102SN/A# licensed hereunder.  You may use the software subject to the license
117102SN/A# terms below provided that you ensure that this notice is replicated
127102SN/A# unmodified and in its entirety in all distributions of the software,
137102SN/A# modified or unmodified, in source code or in binary form.
147102SN/A#
156019SN/A# Redistribution and use in source and binary forms, with or without
166019SN/A# modification, are permitted provided that the following conditions are
176019SN/A# met: redistributions of source code must retain the above copyright
186019SN/A# notice, this list of conditions and the following disclaimer;
196019SN/A# redistributions in binary form must reproduce the above copyright
206019SN/A# notice, this list of conditions and the following disclaimer in the
216019SN/A# documentation and/or other materials provided with the distribution;
226019SN/A# neither the name of the copyright holders nor the names of its
236019SN/A# contributors may be used to endorse or promote products derived from
246019SN/A# this software without specific prior written permission.
256019SN/A#
266019SN/A# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
276019SN/A# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
286019SN/A# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
296019SN/A# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
306019SN/A# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
316019SN/A# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
326019SN/A# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
336019SN/A# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
346019SN/A# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
356019SN/A# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
366019SN/A# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
376019SN/A#
386019SN/A# Authors: Andreas Sandberg
396019SN/A
406019SN/Aimport subprocess
416019SN/Afrom threading import Timer
426019SN/Aimport time
436019SN/Aimport re
446019SN/A
456019SN/Aclass CallTimeoutException(Exception):
466019SN/A    """Exception that indicates that a process call timed out"""
476019SN/A
486019SN/A    def __init__(self, status, stdout, stderr):
496019SN/A        self.status = status
506019SN/A        self.stdout = stdout
516019SN/A        self.stderr = stderr
526310SN/A
537433Sgblack@eecs.umich.educlass ProcessHelper(subprocess.Popen):
547191Sgblack@eecs.umich.edu    """Helper class to run child processes.
557191Sgblack@eecs.umich.edu
566268SN/A    This class wraps a subprocess.Popen class and adds support for
576268SN/A    using it in a with block. When the process goes out of scope, it's
586268SN/A    automatically terminated.
596268SN/A
607161Sgblack@eecs.umich.edu    with ProcessHelper(["/bin/ls"], stdout=subprocess.PIPE) as p:
617206Sgblack@eecs.umich.edu        return p.call()
626019SN/A    """
637129Sgblack@eecs.umich.edu    def __init__(self, *args, **kwargs):
646019SN/A        super(ProcessHelper, self).__init__(*args, **kwargs)
656268SN/A
667139Sgblack@eecs.umich.edu    def _terminate_nicely(self, timeout=5):
677161Sgblack@eecs.umich.edu        def on_timeout():
687161Sgblack@eecs.umich.edu            self.kill()
697203Sgblack@eecs.umich.edu
707344SAli.Saidi@ARM.com        if self.returncode is not None:
717344SAli.Saidi@ARM.com            return self.returncode
727161Sgblack@eecs.umich.edu
737161Sgblack@eecs.umich.edu        timer = Timer(timeout, on_timeout)
747161Sgblack@eecs.umich.edu        self.terminate()
7512258Sgiacomo.travaglini@arm.com        status = self.wait()
767195Sgblack@eecs.umich.edu        timer.cancel()
7710037SARM gem5 Developers
7810037SARM gem5 Developers        return status
7912542Sgiacomo.travaglini@arm.com
8010037SARM gem5 Developers    def __enter__(self):
8110037SARM gem5 Developers        return self
826268SN/A
837161Sgblack@eecs.umich.edu    def __exit__(self, exc_type, exc_value, traceback):
846268SN/A        if self.returncode is None:
856268SN/A            self._terminate_nicely()
866268SN/A
876268SN/A    def call(self, timeout=0):
887139Sgblack@eecs.umich.edu        self._timeout = False
897418Sgblack@eecs.umich.edu        def on_timeout():
906268SN/A            self._timeout = True
917120Sgblack@eecs.umich.edu            self._terminate_nicely()
926268SN/A
937120Sgblack@eecs.umich.edu        status, stdout, stderr = None, None, None
947161Sgblack@eecs.umich.edu        timer = Timer(timeout, on_timeout)
957194Sgblack@eecs.umich.edu        if timeout:
967210Sgblack@eecs.umich.edu            timer.start()
977161Sgblack@eecs.umich.edu
987732SAli.Saidi@ARM.com        stdout, stderr = self.communicate()
997732SAli.Saidi@ARM.com        status = self.wait()
1007732SAli.Saidi@ARM.com
1017732SAli.Saidi@ARM.com        timer.cancel()
1027732SAli.Saidi@ARM.com
1037732SAli.Saidi@ARM.com        if self._timeout:
1046269SN/A            self._terminate_nicely()
1056268SN/A            raise CallTimeoutException(self.returncode, stdout, stderr)
1067134Sgblack@eecs.umich.edu        else:
1076268SN/A            return status, stdout, stderr
1087152Sgblack@eecs.umich.edu
1097152Sgblack@eecs.umich.educlass FileIgnoreList(object):
1106268SN/A    """Helper class to implement file ignore lists.
1116268SN/A
1127334Sgblack@eecs.umich.edu    This class implements ignore lists using plain string matching and
11310037SARM gem5 Developers    regular expressions. In the simplest use case, rules are created
11410037SARM gem5 Developers    statically upon initialization:
11510037SARM gem5 Developers
11610037SARM gem5 Developers        ignore_list = FileIgnoreList(name=("ignore_me.txt", ), rex=(r".*~", )
1176268SN/A
1186268SN/A    Ignores can be queried using in the same ways as normal Python
1196743SN/A    containers:
1206743SN/A
1217363Sgblack@eecs.umich.edu        if file_name in ignore_list:
1226743SN/A            print "Ignoring %s" % file_name
1236743SN/A
1247732SAli.Saidi@ARM.com
1257321Sgblack@eecs.umich.edu    New rules can be added at runtime by extending the list in the
1268868SMatt.Horsnell@arm.com    rules attribute:
1277269Sgblack@eecs.umich.edu
1286743SN/A        ignore_list.rules.append(FileIgnoreList.simple("bar.txt"))
1296743SN/A    """
1306743SN/A
1317199Sgblack@eecs.umich.edu    @staticmethod
1326743SN/A    def simple(r):
1336743SN/A        return lambda f: f == r
1346268SN/A
1356268SN/A    @staticmethod
1367191Sgblack@eecs.umich.edu    def rex(r):
1376019SN/A        re_obj = r if hasattr(r, "search") else re.compile(r)
138        return lambda name: re_obj.search(name)
139
140    def __init__(self, names=(), rex=()):
141        self.rules = [ FileIgnoreList.simple(n) for n in names ] + \
142                     [ FileIgnoreList.rex(r) for r in rex ]
143
144    def __contains__(self, name):
145        for rule in self.rules:
146            if rule(name):
147                return True
148        return False
149
150if __name__ == "__main__":
151    # Run internal self tests to ensure that the helpers are working
152    # properly. The expected output when running this script is
153    # "SUCCESS!".
154
155    cmd_foo = [ "/bin/echo", "-n", "foo" ]
156    cmd_sleep = [ "/bin/sleep", "10" ]
157
158    # Test that things don't break if the process hasn't been started
159    with ProcessHelper(cmd_foo) as p:
160        pass
161
162    with ProcessHelper(cmd_foo, stdout=subprocess.PIPE) as p:
163        status, stdout, stderr = p.call()
164    assert stdout == "foo"
165    assert status == 0
166
167    try:
168        with ProcessHelper(cmd_sleep) as p:
169            status, stdout, stderr = p.call(timeout=1)
170        assert False, "Timeout not triggered"
171    except CallTimeoutException:
172        pass
173
174    ignore_list = FileIgnoreList(
175        names=("ignore.txt", "foo/test.txt"),
176        rex=(r"~$", re.compile("^#")))
177
178    assert "ignore.txt" in ignore_list
179    assert "bar.txt" not in ignore_list
180    assert "foo/test.txt" in ignore_list
181    assert "test.txt" not in ignore_list
182    assert "file1.c~" in ignore_list
183    assert "file1.c" not in ignore_list
184    assert "#foo" in ignore_list
185    assert "foo#" not in ignore_list
186
187    ignore_list.rules.append(FileIgnoreList.simple("bar.txt"))
188    assert "bar.txt" in ignore_list
189
190    print "SUCCESS!"
191