units.py revision 12142
111828Sjason@lowepower.com#!/usr/bin/env python2
211482Sandreas.sandberg@arm.com#
311482Sandreas.sandberg@arm.com# Copyright (c) 2016 ARM Limited
411482Sandreas.sandberg@arm.com# All rights reserved
511482Sandreas.sandberg@arm.com#
611482Sandreas.sandberg@arm.com# The license below extends only to copyright in the software and shall
711482Sandreas.sandberg@arm.com# not be construed as granting a license to any other intellectual
811482Sandreas.sandberg@arm.com# property including but not limited to intellectual property relating
911482Sandreas.sandberg@arm.com# to a hardware implementation of the functionality of the software
1011482Sandreas.sandberg@arm.com# licensed hereunder.  You may use the software subject to the license
1111482Sandreas.sandberg@arm.com# terms below provided that you ensure that this notice is replicated
1211482Sandreas.sandberg@arm.com# unmodified and in its entirety in all distributions of the software,
1311482Sandreas.sandberg@arm.com# modified or unmodified, in source code or in binary form.
1411482Sandreas.sandberg@arm.com#
1511482Sandreas.sandberg@arm.com# Redistribution and use in source and binary forms, with or without
1611482Sandreas.sandberg@arm.com# modification, are permitted provided that the following conditions are
1711482Sandreas.sandberg@arm.com# met: redistributions of source code must retain the above copyright
1811482Sandreas.sandberg@arm.com# notice, this list of conditions and the following disclaimer;
1911482Sandreas.sandberg@arm.com# redistributions in binary form must reproduce the above copyright
2011482Sandreas.sandberg@arm.com# notice, this list of conditions and the following disclaimer in the
2111482Sandreas.sandberg@arm.com# documentation and/or other materials provided with the distribution;
2211482Sandreas.sandberg@arm.com# neither the name of the copyright holders nor the names of its
2311482Sandreas.sandberg@arm.com# contributors may be used to endorse or promote products derived from
2411482Sandreas.sandberg@arm.com# this software without specific prior written permission.
2511482Sandreas.sandberg@arm.com#
2611482Sandreas.sandberg@arm.com# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2711482Sandreas.sandberg@arm.com# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2811482Sandreas.sandberg@arm.com# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2911482Sandreas.sandberg@arm.com# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3011482Sandreas.sandberg@arm.com# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3111482Sandreas.sandberg@arm.com# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3211482Sandreas.sandberg@arm.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3311482Sandreas.sandberg@arm.com# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3411482Sandreas.sandberg@arm.com# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3511482Sandreas.sandberg@arm.com# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3611482Sandreas.sandberg@arm.com# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3711482Sandreas.sandberg@arm.com#
3811482Sandreas.sandberg@arm.com# Authors: Andreas Sandberg
3911482Sandreas.sandberg@arm.com
4011482Sandreas.sandberg@arm.comfrom abc import ABCMeta, abstractmethod
4111482Sandreas.sandberg@arm.comfrom datetime import datetime
4211482Sandreas.sandberg@arm.comimport difflib
4311482Sandreas.sandberg@arm.comimport functools
4411482Sandreas.sandberg@arm.comimport os
4511482Sandreas.sandberg@arm.comimport re
4611482Sandreas.sandberg@arm.comimport subprocess
4711482Sandreas.sandberg@arm.comimport sys
4811482Sandreas.sandberg@arm.comimport traceback
4911482Sandreas.sandberg@arm.com
5011482Sandreas.sandberg@arm.comfrom results import UnitResult
5111482Sandreas.sandberg@arm.comfrom helpers import *
5211482Sandreas.sandberg@arm.com
5311482Sandreas.sandberg@arm.com_test_base = os.path.join(os.path.dirname(__file__), "..")
5411482Sandreas.sandberg@arm.com
5511482Sandreas.sandberg@arm.comclass TestUnit(object):
5611482Sandreas.sandberg@arm.com    """Base class for all test units.
5711482Sandreas.sandberg@arm.com
5811482Sandreas.sandberg@arm.com    A test unit is a part of a larger test case. Test cases usually
5911482Sandreas.sandberg@arm.com    contain two types of units, run units (run gem5) and verify units
6011482Sandreas.sandberg@arm.com    (diff output files). All unit implementations inherit from this
6111482Sandreas.sandberg@arm.com    class.
6211482Sandreas.sandberg@arm.com
6311482Sandreas.sandberg@arm.com    A unit implementation overrides the _run() method. The test runner
6411482Sandreas.sandberg@arm.com    calls the run() method, which wraps _run() to protect against
6511482Sandreas.sandberg@arm.com    exceptions.
6611482Sandreas.sandberg@arm.com
6711482Sandreas.sandberg@arm.com    """
6811482Sandreas.sandberg@arm.com
6911482Sandreas.sandberg@arm.com    __metaclass__ = ABCMeta
7011482Sandreas.sandberg@arm.com
7111482Sandreas.sandberg@arm.com    def __init__(self, name, ref_dir, test_dir, skip=False):
7211482Sandreas.sandberg@arm.com        self.name = name
7311482Sandreas.sandberg@arm.com        self.ref_dir = ref_dir
7411482Sandreas.sandberg@arm.com        self.test_dir = test_dir
7511482Sandreas.sandberg@arm.com        self.force_skip = skip
7611482Sandreas.sandberg@arm.com        self.start_time = None
7711482Sandreas.sandberg@arm.com        self.stop_time = None
7811482Sandreas.sandberg@arm.com
7911482Sandreas.sandberg@arm.com    def result(self, state, **kwargs):
8011482Sandreas.sandberg@arm.com        if self.start_time is not None and "runtime" not in kwargs:
8111482Sandreas.sandberg@arm.com            self.stop_time = datetime.utcnow()
8211482Sandreas.sandberg@arm.com            delta = self.stop_time - self.start_time
8311482Sandreas.sandberg@arm.com            kwargs["runtime"] = delta.total_seconds()
8411482Sandreas.sandberg@arm.com
8511482Sandreas.sandberg@arm.com        return UnitResult(self.name, state, **kwargs)
8611482Sandreas.sandberg@arm.com
8711482Sandreas.sandberg@arm.com    def ok(self, **kwargs):
8811482Sandreas.sandberg@arm.com        return self.result(UnitResult.STATE_OK, **kwargs)
8911482Sandreas.sandberg@arm.com
9011482Sandreas.sandberg@arm.com    def skip(self, **kwargs):
9111482Sandreas.sandberg@arm.com        return self.result(UnitResult.STATE_SKIPPED, **kwargs)
9211482Sandreas.sandberg@arm.com
9311482Sandreas.sandberg@arm.com    def error(self, message, **kwargs):
9411482Sandreas.sandberg@arm.com        return self.result(UnitResult.STATE_ERROR, message=message, **kwargs)
9511482Sandreas.sandberg@arm.com
9611482Sandreas.sandberg@arm.com    def failure(self, message, **kwargs):
9711482Sandreas.sandberg@arm.com        return self.result(UnitResult.STATE_FAILURE, message=message, **kwargs)
9811482Sandreas.sandberg@arm.com
9911482Sandreas.sandberg@arm.com    def ref_file(self, fname):
10011482Sandreas.sandberg@arm.com        return os.path.join(self.ref_dir, fname)
10111482Sandreas.sandberg@arm.com
10211482Sandreas.sandberg@arm.com    def out_file(self, fname):
10311482Sandreas.sandberg@arm.com        return os.path.join(self.test_dir, fname)
10411482Sandreas.sandberg@arm.com
10511482Sandreas.sandberg@arm.com    def _read_output(self, fname, default=""):
10611482Sandreas.sandberg@arm.com        try:
10711482Sandreas.sandberg@arm.com            with open(self.out_file(fname), "r") as f:
10811482Sandreas.sandberg@arm.com                return f.read()
10911482Sandreas.sandberg@arm.com        except IOError:
11011482Sandreas.sandberg@arm.com            return default
11111482Sandreas.sandberg@arm.com
11211482Sandreas.sandberg@arm.com    def run(self):
11311482Sandreas.sandberg@arm.com        self.start_time = datetime.utcnow()
11411482Sandreas.sandberg@arm.com        try:
11511482Sandreas.sandberg@arm.com            if self.force_skip:
11611482Sandreas.sandberg@arm.com                return self.skip()
11711482Sandreas.sandberg@arm.com            else:
11811482Sandreas.sandberg@arm.com                return self._run()
11911482Sandreas.sandberg@arm.com        except:
12011482Sandreas.sandberg@arm.com            return self.error("Python exception:\n%s" % traceback.format_exc())
12111482Sandreas.sandberg@arm.com
12211482Sandreas.sandberg@arm.com    @abstractmethod
12311482Sandreas.sandberg@arm.com    def _run(self):
12411482Sandreas.sandberg@arm.com        pass
12511482Sandreas.sandberg@arm.com
12611482Sandreas.sandberg@arm.comclass RunGem5(TestUnit):
12711482Sandreas.sandberg@arm.com    """Test unit representing a gem5 run.
12811482Sandreas.sandberg@arm.com
12911482Sandreas.sandberg@arm.com    Possible failure modes:
13011482Sandreas.sandberg@arm.com       - gem5 failed to run -> STATE_ERROR
13111482Sandreas.sandberg@arm.com       - timeout -> STATE_ERROR
13211482Sandreas.sandberg@arm.com       - non-zero exit code -> STATE_ERROR
13311482Sandreas.sandberg@arm.com
13411482Sandreas.sandberg@arm.com    Possible non-failure results:
13511482Sandreas.sandberg@arm.com       - exit code == 0 -> STATE_OK
13611482Sandreas.sandberg@arm.com       - exit code == 2 -> STATE_SKIPPED
13711482Sandreas.sandberg@arm.com    """
13811482Sandreas.sandberg@arm.com
13911482Sandreas.sandberg@arm.com    def __init__(self, gem5, gem5_args, timeout=0, **kwargs):
14011482Sandreas.sandberg@arm.com        super(RunGem5, self).__init__("gem5", **kwargs)
14111482Sandreas.sandberg@arm.com        self.gem5 = gem5
14211482Sandreas.sandberg@arm.com        self.args = gem5_args
14311482Sandreas.sandberg@arm.com        self.timeout = timeout
14411482Sandreas.sandberg@arm.com
14511482Sandreas.sandberg@arm.com    def _run(self):
14611482Sandreas.sandberg@arm.com        gem5_cmd = [
14711482Sandreas.sandberg@arm.com            self.gem5,
14811482Sandreas.sandberg@arm.com            "-d", self.test_dir,
14911879Sandreas.sandberg@arm.com            "--stats-file", "text://stats.txt?desc=False",
15011482Sandreas.sandberg@arm.com            "-re",
15111482Sandreas.sandberg@arm.com        ] + self.args
15211482Sandreas.sandberg@arm.com
15311482Sandreas.sandberg@arm.com        try:
15411482Sandreas.sandberg@arm.com            with ProcessHelper(gem5_cmd, stdout=subprocess.PIPE,
15511482Sandreas.sandberg@arm.com                               stderr=subprocess.PIPE) as p:
15611482Sandreas.sandberg@arm.com                status, gem5_stdout, gem5_stderr = p.call(timeout=self.timeout)
15711482Sandreas.sandberg@arm.com        except CallTimeoutException as te:
15811482Sandreas.sandberg@arm.com            return self.error("Timeout", stdout=te.stdout, stderr=te.stderr)
15911482Sandreas.sandberg@arm.com        except OSError as ose:
16011482Sandreas.sandberg@arm.com            return self.error("Failed to launch gem5: %s" % ose)
16111482Sandreas.sandberg@arm.com
16211482Sandreas.sandberg@arm.com        stderr = "\n".join([
16311482Sandreas.sandberg@arm.com            "*** gem5 stderr ***",
16411482Sandreas.sandberg@arm.com            gem5_stderr,
16511482Sandreas.sandberg@arm.com            "",
16611482Sandreas.sandberg@arm.com            "*** m5out/simerr ***",
16711482Sandreas.sandberg@arm.com            self._read_output("simerr"),
16811482Sandreas.sandberg@arm.com        ])
16911482Sandreas.sandberg@arm.com
17011482Sandreas.sandberg@arm.com        stdout = "\n".join([
17111482Sandreas.sandberg@arm.com            "*** gem5 stdout ***",
17211482Sandreas.sandberg@arm.com            gem5_stdout,
17311482Sandreas.sandberg@arm.com            "",
17411482Sandreas.sandberg@arm.com            "*** m5out/simout ***",
17511482Sandreas.sandberg@arm.com            self._read_output("simout"),
17611482Sandreas.sandberg@arm.com        ])
17711482Sandreas.sandberg@arm.com
17811482Sandreas.sandberg@arm.com        # Signal
17911482Sandreas.sandberg@arm.com        if status < 0:
18011482Sandreas.sandberg@arm.com            return self.error("gem5 terminated by signal %i" % (-status, ),
18111482Sandreas.sandberg@arm.com                              stdout=stdout, stderr=stderr)
18211482Sandreas.sandberg@arm.com        elif status == 2:
18311482Sandreas.sandberg@arm.com            return self.skip(stdout=stdout, stderr=stderr)
18411482Sandreas.sandberg@arm.com        elif status > 0:
18511482Sandreas.sandberg@arm.com            return self.error("gem5 exited with non-zero status: %i" % status,
18611482Sandreas.sandberg@arm.com                              stdout=stdout, stderr=stderr)
18711482Sandreas.sandberg@arm.com        else:
18811482Sandreas.sandberg@arm.com            return self.ok(stdout=stdout, stderr=stderr)
18911482Sandreas.sandberg@arm.com
19011482Sandreas.sandberg@arm.comclass DiffOutFile(TestUnit):
19111482Sandreas.sandberg@arm.com    """Test unit comparing and output file and a reference file."""
19211482Sandreas.sandberg@arm.com
19311482Sandreas.sandberg@arm.com    # regular expressions of lines to ignore when diffing outputs
19411482Sandreas.sandberg@arm.com    diff_ignore_regexes = {
19511482Sandreas.sandberg@arm.com        "simout" : [
19611482Sandreas.sandberg@arm.com            re.compile('^Redirecting (stdout|stderr) to'),
19711482Sandreas.sandberg@arm.com            re.compile('^gem5 compiled '),
19811482Sandreas.sandberg@arm.com            re.compile('^gem5 started '),
19911482Sandreas.sandberg@arm.com            re.compile('^gem5 executing on '),
20011482Sandreas.sandberg@arm.com            re.compile('^command line:'),
20111482Sandreas.sandberg@arm.com            re.compile("^Couldn't import dot_parser,"),
20211482Sandreas.sandberg@arm.com            re.compile("^info: kernel located at:"),
20311482Sandreas.sandberg@arm.com            re.compile("^Couldn't unlink "),
20411482Sandreas.sandberg@arm.com            re.compile("^Using GPU kernel code file\(s\) "),
20511482Sandreas.sandberg@arm.com        ],
20611482Sandreas.sandberg@arm.com        "simerr" : [
20711482Sandreas.sandberg@arm.com            #re.compile('^Simulation complete at'),
20811482Sandreas.sandberg@arm.com        ],
20911482Sandreas.sandberg@arm.com        "config.ini" : [
21011482Sandreas.sandberg@arm.com            re.compile("^(executable|readfile|kernel|image_file)="),
21111482Sandreas.sandberg@arm.com            re.compile("^(cwd|input|codefile)="),
21211482Sandreas.sandberg@arm.com        ],
21311482Sandreas.sandberg@arm.com        "config.json" : [
21411482Sandreas.sandberg@arm.com            re.compile(r'''^\s*"(executable|readfile|kernel|image_file)":'''),
21511482Sandreas.sandberg@arm.com            re.compile(r'''^\s*"(cwd|input|codefile)":'''),
21611482Sandreas.sandberg@arm.com        ],
21711482Sandreas.sandberg@arm.com    }
21811482Sandreas.sandberg@arm.com
21911482Sandreas.sandberg@arm.com    def __init__(self, fname, **kwargs):
22011482Sandreas.sandberg@arm.com        super(DiffOutFile, self).__init__("diff[%s]" % fname,
22111482Sandreas.sandberg@arm.com                                          **kwargs)
22211482Sandreas.sandberg@arm.com
22311482Sandreas.sandberg@arm.com        self.fname = fname
22411482Sandreas.sandberg@arm.com        self.line_filters = DiffOutFile.diff_ignore_regexes.get(fname, tuple())
22511482Sandreas.sandberg@arm.com
22611482Sandreas.sandberg@arm.com    def _filter_file(self, fname):
22711482Sandreas.sandberg@arm.com        def match_line(l):
22811482Sandreas.sandberg@arm.com            for r in self.line_filters:
22911482Sandreas.sandberg@arm.com                if r.match(l):
23011482Sandreas.sandberg@arm.com                    return True
23111482Sandreas.sandberg@arm.com            return False
23211482Sandreas.sandberg@arm.com
23311482Sandreas.sandberg@arm.com        with open(fname, "r") as f:
23411482Sandreas.sandberg@arm.com            for l in f:
23511482Sandreas.sandberg@arm.com                if not match_line(l):
23611482Sandreas.sandberg@arm.com                    yield l
23711482Sandreas.sandberg@arm.com
23811482Sandreas.sandberg@arm.com
23911482Sandreas.sandberg@arm.com    def _run(self):
24011482Sandreas.sandberg@arm.com        fname = self.fname
24111482Sandreas.sandberg@arm.com        ref = self.ref_file(fname)
24211482Sandreas.sandberg@arm.com        out = self.out_file(fname)
24311482Sandreas.sandberg@arm.com
24411482Sandreas.sandberg@arm.com        if not os.path.exists(ref):
24511482Sandreas.sandberg@arm.com            return self.error("%s doesn't exist in reference directory" \
24611482Sandreas.sandberg@arm.com                              % fname)
24711482Sandreas.sandberg@arm.com
24811482Sandreas.sandberg@arm.com        if not os.path.exists(out):
24911482Sandreas.sandberg@arm.com            return self.error("%s doesn't exist in output directory" % fname)
25011482Sandreas.sandberg@arm.com
25111482Sandreas.sandberg@arm.com        diff = difflib.unified_diff(
25211482Sandreas.sandberg@arm.com            tuple(self._filter_file(ref)),
25311482Sandreas.sandberg@arm.com            tuple(self._filter_file(out)),
25411482Sandreas.sandberg@arm.com            fromfile="ref/%s" % fname, tofile="out/%s" % fname)
25511482Sandreas.sandberg@arm.com
25611482Sandreas.sandberg@arm.com        diff = list(diff)
25711482Sandreas.sandberg@arm.com        if diff:
25811482Sandreas.sandberg@arm.com            return self.error("ref/%s and out/%s differ" % (fname, fname),
25911482Sandreas.sandberg@arm.com                              stderr="".join(diff))
26011482Sandreas.sandberg@arm.com        else:
26111482Sandreas.sandberg@arm.com            return self.ok(stdout="-- ref/%s and out/%s are identical --" \
26211482Sandreas.sandberg@arm.com                           % (fname, fname))
26311482Sandreas.sandberg@arm.com
26411482Sandreas.sandberg@arm.comclass DiffStatFile(TestUnit):
26511482Sandreas.sandberg@arm.com    """Test unit comparing two gem5 stat files."""
26611482Sandreas.sandberg@arm.com
26711482Sandreas.sandberg@arm.com    def __init__(self, **kwargs):
26811482Sandreas.sandberg@arm.com        super(DiffStatFile, self).__init__("stat_diff", **kwargs)
26911482Sandreas.sandberg@arm.com
27011482Sandreas.sandberg@arm.com        self.stat_diff = os.path.join(_test_base, "diff-out")
27111482Sandreas.sandberg@arm.com
27211482Sandreas.sandberg@arm.com    def _run(self):
27312142Sandreas.sandberg@arm.com        STATUS_OK = 0
27412142Sandreas.sandberg@arm.com        STATUS_NEW_STATS = 1
27512142Sandreas.sandberg@arm.com        STATUS_FAILED = 2
27612142Sandreas.sandberg@arm.com
27711482Sandreas.sandberg@arm.com        stats = "stats.txt"
27811482Sandreas.sandberg@arm.com
27911482Sandreas.sandberg@arm.com        cmd = [
28011482Sandreas.sandberg@arm.com            self.stat_diff,
28111482Sandreas.sandberg@arm.com            self.ref_file(stats), self.out_file(stats),
28211482Sandreas.sandberg@arm.com        ]
28311482Sandreas.sandberg@arm.com        with ProcessHelper(cmd,
28411482Sandreas.sandberg@arm.com                           stdout=subprocess.PIPE,
28511482Sandreas.sandberg@arm.com                           stderr=subprocess.PIPE) as p:
28611482Sandreas.sandberg@arm.com            status, stdout, stderr = p.call()
28711482Sandreas.sandberg@arm.com
28812142Sandreas.sandberg@arm.com        if status in (STATUS_OK, STATUS_NEW_STATS):
28911482Sandreas.sandberg@arm.com            return self.ok(stdout=stdout, stderr=stderr)
29012142Sandreas.sandberg@arm.com        elif status == STATUS_FAILED:
29111482Sandreas.sandberg@arm.com            return self.failure("Statistics mismatch",
29211482Sandreas.sandberg@arm.com                                stdout=stdout, stderr=stderr)
29311482Sandreas.sandberg@arm.com        else:
29411482Sandreas.sandberg@arm.com            return self.error("diff-out returned an error: %i" % status,
29511482Sandreas.sandberg@arm.com                              stdout=stdout, stderr=stderr)
296