#!/usr/bin/env python2.7 # # Copyright (c) 2016-2017 ARM Limited # All rights reserved # # The license below extends only to copyright in the software and shall # not be construed as granting a license to any other intellectual # property including but not limited to intellectual property relating # to a hardware implementation of the functionality of the software # licensed hereunder. You may use the software subject to the license # terms below provided that you ensure that this notice is replicated # unmodified and in its entirety in all distributions of the software, # modified or unmodified, in source code or in binary form. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer; # redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution; # neither the name of the copyright holders nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Authors: Andreas Sandberg from abc import ABCMeta, abstractmethod import os from collections import namedtuple from units import * from results import TestResult import shutil _test_base = os.path.join(os.path.dirname(__file__), "..") ClassicConfig = namedtuple("ClassicConfig", ( "category", "mode", "workload", "isa", "os", "config", )) # There are currently two "classes" of test # configurations. Architecture-specific ones and generic ones # (typically SE mode tests). In both cases, the configuration name # matches a file in tests/configs/ that will be picked up by the test # runner (run.py). # # Architecture specific configurations are listed in the arch_configs # dictionary. This is indexed by a (cpu architecture, gpu # architecture) tuple. GPU architecture is optional and may be None. # # Generic configurations are listed in the generic_configs tuple. # # When discovering available test cases, this script look uses the # test list as a list of /candidate/ configurations. A configuration # is only used if a test has a reference output for that # configuration. In addition to the base configurations from # arch_configs and generic_configs, a Ruby configuration may be # appended to the base name (this is probed /in addition/ to the # original name. See get_tests() for details. # arch_configs = { ("alpha", None) : ( 'tsunami-simple-atomic', 'tsunami-simple-timing', 'tsunami-simple-atomic-dual', 'tsunami-simple-timing-dual', 'twosys-tsunami-simple-atomic', 'tsunami-o3', 'tsunami-o3-dual', 'tsunami-minor', 'tsunami-minor-dual', 'tsunami-switcheroo-full', ), ("arm", None) : ( 'simple-atomic-dummychecker', 'o3-timing-checker', 'realview-simple-atomic', 'realview-simple-atomic-dual', 'realview-simple-atomic-checkpoint', 'realview-simple-timing', 'realview-simple-timing-dual', 'realview-o3', 'realview-o3-checker', 'realview-o3-dual', 'realview-minor', 'realview-minor-dual', 'realview-switcheroo-atomic', 'realview-switcheroo-timing', 'realview-switcheroo-noncaching-timing', 'realview-switcheroo-o3', 'realview-switcheroo-full', 'realview64-simple-atomic', 'realview64-simple-atomic-checkpoint', 'realview64-simple-atomic-dual', 'realview64-simple-timing', 'realview64-simple-timing-dual', 'realview64-o3', 'realview64-o3-checker', 'realview64-o3-dual', 'realview64-minor', 'realview64-minor-dual', 'realview64-switcheroo-atomic', 'realview64-switcheroo-timing', 'realview64-switcheroo-o3', 'realview64-switcheroo-full', ), ("sparc", None) : ( 't1000-simple-atomic', 't1000-simple-x86', ), ("timing", None) : ( 'pc-simple-atomic', 'pc-simple-timing', 'pc-o3-timing', 'pc-switcheroo-full', ), ("x86", "hsail") : ( 'gpu', ), } generic_configs = ( 'simple-atomic', 'simple-atomic-mp', 'simple-timing', 'simple-timing-mp', 'minor-timing', 'minor-timing-mp', 'o3-timing', 'o3-timing-mt', 'o3-timing-mp', 'rubytest', 'memcheck', 'memtest', 'memtest-filter', 'tgen-simple-mem', 'tgen-dram-ctrl', 'dram-lowp', 'learning-gem5-p1-simple', 'learning-gem5-p1-two-level', ) default_ruby_protocol = { "arm" : "MOESI_CMP_directory", } def get_default_protocol(arch): return default_ruby_protocol.get(arch, 'MI_example') all_categories = ("quick", "long") all_modes = ("fs", "se") class Test(object): """Test case base class. Test cases consists of one or more test units that are run in two phases. A run phase (units produced by run_units() and a verify phase (units from verify_units()). The verify phase is skipped if the run phase fails. """ __metaclass__ = ABCMeta def __init__(self, name): self.test_name = name @abstractmethod def ref_files(self): """Get a list of reference files used by this test case""" pass @abstractmethod def run_units(self): """Units (typically RunGem5 instances) that describe the run phase of this test. """ pass @abstractmethod def verify_units(self): """Verify the output from the run phase (see run_units()).""" pass @abstractmethod def update_ref(self): """Update reference files with files from a test run""" pass def run(self): """Run this test case and return a list of results""" run_results = [ u.run() for u in self.run_units() ] run_ok = all([not r.skipped() and r for r in run_results ]) verify_results = [ u.run() if run_ok else u.skip() for u in self.verify_units() ] return TestResult(self.test_name, run_results=run_results, verify_results=verify_results) def __str__(self): return self.test_name class ClassicTest(Test): # The diff ignore list contains all files that shouldn't be diffed # using DiffOutFile. These files typically use special-purpose # diff tools (e.g., DiffStatFile). diff_ignore_files = FileIgnoreList( names=( # Stat files use a special stat differ "stats.txt", ), rex=( )) # These files should never be included in the list of # reference files. This list should include temporary files # and other files that we don't care about. ref_ignore_files = FileIgnoreList( names=( "EMPTY", ), rex=( # Mercurial sometimes leaves backups when applying MQ patches r"\.orig$", r"\.rej$", )) def __init__(self, gem5, output_dir, config_tuple, timeout=None, skip=False, skip_diff_out=False, skip_diff_stat=False): super(ClassicTest, self).__init__("/".join(config_tuple)) ct = config_tuple self.gem5 = os.path.abspath(gem5) self.script = os.path.join(_test_base, "run.py") self.config_tuple = ct self.timeout = timeout self.output_dir = output_dir self.ref_dir = os.path.join(_test_base, ct.category, ct.mode, ct.workload, "ref", ct.isa, ct.os, ct.config) self.skip_run = skip self.skip_diff_out = skip or skip_diff_out self.skip_diff_stat = skip or skip_diff_stat def ref_files(self): ref_dir = os.path.abspath(self.ref_dir) for root, dirs, files in os.walk(ref_dir, topdown=False): for f in files: fpath = os.path.join(root[len(ref_dir) + 1:], f) if fpath not in ClassicTest.ref_ignore_files: yield fpath def run_units(self): args = [ self.script, "/".join(self.config_tuple), ] return [ RunGem5(self.gem5, args, ref_dir=self.ref_dir, test_dir=self.output_dir, skip=self.skip_run), ] def verify_units(self): ref_files = set(self.ref_files()) units = [] if "stats.txt" in ref_files: units.append( DiffStatFile(ref_dir=self.ref_dir, test_dir=self.output_dir, skip=self.skip_diff_stat)) units += [ DiffOutFile(f, ref_dir=self.ref_dir, test_dir=self.output_dir, skip=self.skip_diff_out) for f in ref_files if f not in ClassicTest.diff_ignore_files ] return units def update_ref(self): for fname in self.ref_files(): shutil.copy( os.path.join(self.output_dir, fname), os.path.join(self.ref_dir, fname)) def parse_test_filter(test_filter): wildcards = ("", "*") _filter = list(test_filter.split("/")) if len(_filter) > 3: raise RuntimeError("Illegal test filter string") _filter += [ "", ] * (3 - len(_filter)) isa, cat, mode = _filter if isa in wildcards: raise RuntimeError("No ISA specified") cat = all_categories if cat in wildcards else (cat, ) mode = all_modes if mode in wildcards else (mode, ) return isa, cat, mode def get_tests(isa, categories=all_categories, modes=all_modes, ruby_protocol=None, gpu_isa=None): # Generate a list of candidate configs configs = list(arch_configs.get((isa, gpu_isa), [])) if (isa, gpu_isa) == ("x86", "hsail"): if ruby_protocol == "GPU_RfO": configs += ['gpu-randomtest'] else: configs += generic_configs if ruby_protocol == get_default_protocol(isa): if ruby_protocol == 'MI_example': configs += [ "%s-ruby" % (c, ) for c in configs ] else: configs += [ "%s-ruby-%s" % (c, ruby_protocol) for c in configs ] elif ruby_protocol is not None: # Override generic ISA configs when using Ruby (excluding # MI_example which is included in all ISAs by default). This # reduces the number of generic tests we re-run for when # compiling Ruby targets. configs = [ "%s-ruby-%s" % (c, ruby_protocol) for c in configs ] # /(quick|long)/(fs|se)/workload/ref/arch/guest/config/ for conf_script in configs: for cat in categories: for mode in modes: mode_dir = os.path.join(_test_base, cat, mode) if not os.path.exists(mode_dir): continue for workload in os.listdir(mode_dir): isa_dir = os.path.join(mode_dir, workload, "ref", isa) if not os.path.isdir(isa_dir): continue for _os in os.listdir(isa_dir): test_dir = os.path.join(isa_dir, _os, conf_script) if not os.path.exists(test_dir) or \ os.path.exists(os.path.join(test_dir, "skip")): continue yield ClassicConfig(cat, mode, workload, isa, _os, conf_script)