tests.py revision 12268
1#!/usr/bin/env python2
2#
3# Copyright (c) 2016-2017 ARM Limited
4# All rights reserved
5#
6# The license below extends only to copyright in the software and shall
7# not be construed as granting a license to any other intellectual
8# property including but not limited to intellectual property relating
9# to a hardware implementation of the functionality of the software
10# licensed hereunder.  You may use the software subject to the license
11# terms below provided that you ensure that this notice is replicated
12# unmodified and in its entirety in all distributions of the software,
13# modified or unmodified, in source code or in binary form.
14#
15# Redistribution and use in source and binary forms, with or without
16# modification, are permitted provided that the following conditions are
17# met: redistributions of source code must retain the above copyright
18# notice, this list of conditions and the following disclaimer;
19# redistributions in binary form must reproduce the above copyright
20# notice, this list of conditions and the following disclaimer in the
21# documentation and/or other materials provided with the distribution;
22# neither the name of the copyright holders nor the names of its
23# contributors may be used to endorse or promote products derived from
24# this software without specific prior written permission.
25#
26# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37#
38# Authors: Andreas Sandberg
39
40from abc import ABCMeta, abstractmethod
41import os
42from collections import namedtuple
43from units import *
44from results import TestResult
45import shutil
46
47_test_base = os.path.join(os.path.dirname(__file__), "..")
48
49ClassicConfig = namedtuple("ClassicConfig", (
50    "category",
51    "mode",
52    "workload",
53    "isa",
54    "os",
55    "config",
56))
57
58# There are currently two "classes" of test
59# configurations. Architecture-specific ones and generic ones
60# (typically SE mode tests). In both cases, the configuration name
61# matches a file in tests/configs/ that will be picked up by the test
62# runner (run.py).
63#
64# Architecture specific configurations are listed in the arch_configs
65# dictionary. This is indexed by a (cpu architecture, gpu
66# architecture) tuple. GPU architecture is optional and may be None.
67#
68# Generic configurations are listed in the generic_configs tuple.
69#
70# When discovering available test cases, this script look uses the
71# test list as a list of /candidate/ configurations. A configuration
72# is only used if a test has a reference output for that
73# configuration. In addition to the base configurations from
74# arch_configs and generic_configs, a Ruby configuration may be
75# appended to the base name (this is probed /in addition/ to the
76# original name. See get_tests() for details.
77#
78arch_configs = {
79    ("alpha", None) : (
80        'tsunami-simple-atomic',
81        'tsunami-simple-timing',
82        'tsunami-simple-atomic-dual',
83        'tsunami-simple-timing-dual',
84        'twosys-tsunami-simple-atomic',
85        'tsunami-o3', 'tsunami-o3-dual',
86        'tsunami-minor', 'tsunami-minor-dual',
87        'tsunami-switcheroo-full',
88    ),
89
90    ("arm", None) : (
91        'simple-atomic-dummychecker',
92        'o3-timing-checker',
93        'realview-simple-atomic',
94        'realview-simple-atomic-dual',
95        'realview-simple-atomic-checkpoint',
96        'realview-simple-timing',
97        'realview-simple-timing-dual',
98        'realview-o3',
99        'realview-o3-checker',
100        'realview-o3-dual',
101        'realview-minor',
102        'realview-minor-dual',
103        'realview-switcheroo-atomic',
104        'realview-switcheroo-timing',
105        'realview-switcheroo-o3',
106        'realview-switcheroo-full',
107        'realview64-simple-atomic',
108        'realview64-simple-atomic-checkpoint',
109        'realview64-simple-atomic-dual',
110        'realview64-simple-timing',
111        'realview64-simple-timing-dual',
112        'realview64-o3',
113        'realview64-o3-checker',
114        'realview64-o3-dual',
115        'realview64-minor',
116        'realview64-minor-dual',
117        'realview64-switcheroo-atomic',
118        'realview64-switcheroo-timing',
119        'realview64-switcheroo-o3',
120        'realview64-switcheroo-full',
121    ),
122
123    ("sparc", None) : (
124        't1000-simple-atomic',
125        't1000-simple-x86',
126    ),
127
128    ("timing", None) : (
129        'pc-simple-atomic',
130        'pc-simple-timing',
131        'pc-o3-timing',
132        'pc-switcheroo-full',
133    ),
134
135    ("x86", "hsail") : (
136        'gpu',
137    ),
138}
139
140generic_configs = (
141    'simple-atomic',
142    'simple-atomic-mp',
143    'simple-timing',
144    'simple-timing-mp',
145
146    'minor-timing',
147    'minor-timing-mp',
148
149    'o3-timing',
150    'o3-timing-mt',
151    'o3-timing-mp',
152
153    'rubytest',
154    'memcheck',
155    'memtest',
156    'memtest-filter',
157    'tgen-simple-mem',
158    'tgen-dram-ctrl',
159    'dram-lowp',
160
161    'learning-gem5-p1-simple',
162    'learning-gem5-p1-two-level',
163)
164
165default_ruby_protocol = {
166    "arm" : "MOESI_CMP_directory",
167}
168
169def get_default_protocol(arch):
170    return default_ruby_protocol.get(arch, 'MI_example')
171
172all_categories = ("quick", "long")
173all_modes = ("fs", "se")
174
175class Test(object):
176    """Test case base class.
177
178    Test cases consists of one or more test units that are run in two
179    phases. A run phase (units produced by run_units() and a verify
180    phase (units from verify_units()). The verify phase is skipped if
181    the run phase fails.
182
183    """
184
185    __metaclass__ = ABCMeta
186
187    def __init__(self, name):
188        self.test_name = name
189
190    @abstractmethod
191    def ref_files(self):
192        """Get a list of reference files used by this test case"""
193        pass
194
195    @abstractmethod
196    def run_units(self):
197        """Units (typically RunGem5 instances) that describe the run phase of
198        this test.
199
200        """
201        pass
202
203    @abstractmethod
204    def verify_units(self):
205        """Verify the output from the run phase (see run_units())."""
206        pass
207
208    @abstractmethod
209    def update_ref(self):
210        """Update reference files with files from a test run"""
211        pass
212
213    def run(self):
214        """Run this test case and return a list of results"""
215
216        run_results = [ u.run() for u in self.run_units() ]
217        run_ok = all([not r.skipped() and r for r in run_results ])
218
219        verify_results = [
220            u.run() if run_ok else u.skip()
221            for u in self.verify_units()
222        ]
223
224        return TestResult(self.test_name,
225                          run_results=run_results,
226                          verify_results=verify_results)
227
228    def __str__(self):
229        return self.test_name
230
231class ClassicTest(Test):
232    # The diff ignore list contains all files that shouldn't be diffed
233    # using DiffOutFile. These files typically use special-purpose
234    # diff tools (e.g., DiffStatFile).
235    diff_ignore_files = FileIgnoreList(
236        names=(
237            # Stat files use a special stat differ
238            "stats.txt",
239        ), rex=(
240        ))
241
242    # These files should never be included in the list of
243    # reference files. This list should include temporary files
244    # and other files that we don't care about.
245    ref_ignore_files = FileIgnoreList(
246        names=(
247            "EMPTY",
248        ), rex=(
249            # Mercurial sometimes leaves backups when applying MQ patches
250            r"\.orig$",
251            r"\.rej$",
252        ))
253
254    def __init__(self, gem5, output_dir, config_tuple,
255                 timeout=None,
256                 skip=False, skip_diff_out=False, skip_diff_stat=False):
257
258        super(ClassicTest, self).__init__("/".join(config_tuple))
259
260        ct = config_tuple
261
262        self.gem5 = os.path.abspath(gem5)
263        self.script = os.path.join(_test_base, "run.py")
264        self.config_tuple = ct
265        self.timeout = timeout
266
267        self.output_dir = output_dir
268        self.ref_dir = os.path.join(_test_base,
269                                    ct.category, ct.mode, ct.workload,
270                                    "ref", ct.isa, ct.os, ct.config)
271        self.skip_run = skip
272        self.skip_diff_out = skip or skip_diff_out
273        self.skip_diff_stat = skip or skip_diff_stat
274
275    def ref_files(self):
276        ref_dir = os.path.abspath(self.ref_dir)
277        for root, dirs, files in os.walk(ref_dir, topdown=False):
278            for f in files:
279                fpath = os.path.join(root[len(ref_dir) + 1:], f)
280                if fpath not in ClassicTest.ref_ignore_files:
281                    yield fpath
282
283    def run_units(self):
284        args = [
285            self.script,
286            "/".join(self.config_tuple),
287        ]
288
289        return [
290            RunGem5(self.gem5, args,
291                    ref_dir=self.ref_dir, test_dir=self.output_dir,
292                    skip=self.skip_run),
293        ]
294
295    def verify_units(self):
296        ref_files = set(self.ref_files())
297        units = []
298        if "stats.txt" in ref_files:
299            units.append(
300                DiffStatFile(ref_dir=self.ref_dir, test_dir=self.output_dir,
301                             skip=self.skip_diff_stat))
302        units += [
303            DiffOutFile(f,
304                        ref_dir=self.ref_dir, test_dir=self.output_dir,
305                        skip=self.skip_diff_out)
306            for f in ref_files if f not in ClassicTest.diff_ignore_files
307        ]
308
309        return units
310
311    def update_ref(self):
312        for fname in self.ref_files():
313            shutil.copy(
314                os.path.join(self.output_dir, fname),
315                os.path.join(self.ref_dir, fname))
316
317def parse_test_filter(test_filter):
318    wildcards = ("", "*")
319
320    _filter = list(test_filter.split("/"))
321    if len(_filter) > 3:
322        raise RuntimeError("Illegal test filter string")
323    _filter += [ "", ] * (3 - len(_filter))
324
325    isa, cat, mode = _filter
326
327    if isa in wildcards:
328        raise RuntimeError("No ISA specified")
329
330    cat = all_categories if cat in wildcards else (cat, )
331    mode = all_modes if mode in wildcards else (mode, )
332
333    return isa, cat, mode
334
335def get_tests(isa,
336              categories=all_categories, modes=all_modes,
337              ruby_protocol=None, gpu_isa=None):
338
339    # Generate a list of candidate configs
340    configs = list(arch_configs.get((isa, gpu_isa), []))
341
342    if (isa, gpu_isa) == ("x86", "hsail"):
343        if ruby_protocol == "GPU_RfO":
344            configs += ['gpu-randomtest']
345    else:
346        configs += generic_configs
347
348    if ruby_protocol == get_default_protocol(isa):
349        if ruby_protocol == 'MI_example':
350            configs += [ "%s-ruby" % (c, ) for c in configs ]
351        else:
352            configs += [ "%s-ruby-%s" % (c, ruby_protocol) for c in configs ]
353    elif ruby_protocol is not None:
354        # Override generic ISA configs when using Ruby (excluding
355        # MI_example which is included in all ISAs by default). This
356        # reduces the number of generic tests we re-run for when
357        # compiling Ruby targets.
358        configs = [ "%s-ruby-%s" % (c, ruby_protocol) for c in configs ]
359
360    # /(quick|long)/(fs|se)/workload/ref/arch/guest/config/
361    for conf_script in configs:
362        for cat in categories:
363            for mode in modes:
364                mode_dir = os.path.join(_test_base, cat, mode)
365                if not os.path.exists(mode_dir):
366                    continue
367
368                for workload in os.listdir(mode_dir):
369                    isa_dir = os.path.join(mode_dir, workload, "ref", isa)
370                    if not os.path.isdir(isa_dir):
371                        continue
372
373                    for _os in os.listdir(isa_dir):
374                        test_dir = os.path.join(isa_dir, _os, conf_script)
375                        if not os.path.exists(test_dir) or \
376                           os.path.exists(os.path.join(test_dir, "skip")):
377                            continue
378
379                        yield ClassicConfig(cat, mode, workload, isa, _os,
380                                            conf_script)
381