$(error Unrecognised category '$(filter-out $(ALL_CATEGORIES),$(CATEGORY))')
endif
+ifneq ($(VARY-CFG),)
+TEST-CFGS := $(foreach env,$(TEST-ENVS),$(foreach vary,$(VARY-CFG),test-$(env)-$(NAME)~$(vary).cfg))
+else
+TEST-CFGS := $(foreach env,$(TEST-ENVS),test-$(env)-$(NAME).cfg)
+endif
+
.PHONY: build
-build: $(foreach env,$(TEST-ENVS),test-$(env)-$(NAME) test-$(env)-$(NAME).cfg)
+build: $(foreach env,$(TEST-ENVS),test-$(env)-$(NAME)) $(TEST-CFGS)
build: info.json
info.json: $(ROOT)/build/mkinfo.py FORCE
- @$(PYTHON) $< $@.tmp "$(NAME)" "$(CATEGORY)" "$(TEST-ENVS)"
+ @$(PYTHON) $< $@.tmp "$(NAME)" "$(CATEGORY)" "$(TEST-ENVS)" "$(VARY-CFG)"
@$(call move-if-changed,$@.tmp,$@)
.PHONY: install install-each-env
cfg-$(1) ?= $(defcfg-$(1))
-test-$(1)-$(NAME).cfg: $(ROOT)/build/mkcfg.py $$(cfg-$(1)) $(TEST-EXTRA-CFG) FORCE
- @$(PYTHON) $$< $$@.tmp "$$(cfg-$(1))" "$(TEST-EXTRA-CFG)"
+cfg-default-deps := $(ROOT)/build/mkcfg.py $$(cfg-$(1)) $(TEST-EXTRA-CFG) FORCE
+
+test-$(1)-$(NAME).cfg: $$(cfg-default-deps)
+ $(PYTHON) $$< $$@.tmp "$$(cfg-$(1))" "$(TEST-EXTRA-CFG)" ""
+ @$(call move-if-changed,$$@.tmp,$$@)
+
+test-$(1)-$(NAME)~%.cfg: $$(cfg-default-deps) %.cfg.in
+ $(PYTHON) $$< $$@.tmp "$$(cfg-$(1))" "$(TEST-EXTRA-CFG)" "$$*.cfg.in"
+ @$(call move-if-changed,$$@.tmp,$$@)
+
+test-$(1)-$(NAME)~%.cfg: $$(cfg-default-deps) $(ROOT)/config/%.cfg.in
+ $(PYTHON) $$< $$@.tmp "$$(cfg-$(1))" "$(TEST-EXTRA-CFG)" "$(ROOT)/config/$$*.cfg.in"
@$(call move-if-changed,$$@.tmp,$$@)
-include $$(link-$(1):%.lds=%.d)
@$(INSTALL_DIR) $(DESTDIR)$(xtftestdir)/$(NAME)
$(INSTALL_PROGRAM) $$< $(DESTDIR)$(xtftestdir)/$(NAME)
-install-$(1).cfg: test-$(1)-$(NAME).cfg
+install-$(1).cfg: $(TEST-CFGS)
@$(INSTALL_DIR) $(DESTDIR)$(xtftestdir)/$(NAME)
$(INSTALL_DATA) $$< $(DESTDIR)$(xtftestdir)/$(NAME)
.PHONY: clean
clean:
find $(ROOT) \( -name "*.o" -o -name "*.d" \) -delete
- rm -f $(foreach env,$(TEST-ENVS),test-$(env)-$(NAME) test-$(env)-$(NAME).cfg)
+ rm -f $(foreach env,$(TEST-ENVS),test-$(env)-$(NAME) test-$(env)-$(NAME)*.cfg)
.PHONY: %var
%var:
import sys, os
-# Usage: mkcfg.py $OUT $DEFAULT-CFG $EXTRA-CFG
-_, out, defcfg, extracfg = sys.argv
+# Usage: mkcfg.py $OUT $DEFAULT-CFG $EXTRA-CFG $VARY-CFG
+_, out, defcfg, extracfg, varycfg = sys.argv
# Evaluate environment and name from $OUT
_, env, name = out.split('.')[0].split('-', 2)
+# Possibly split apart the variation suffix
+variation = ''
+if '~' in name:
+ parts = name.split('~', 1)
+ name, variation = parts[0], '~' + parts[1]
+
def expand(text):
""" Expand certain variables in text """
return (text
.replace("@@NAME@@", name)
.replace("@@ENV@@", env)
.replace("@@XTFDIR@@", os.environ["xtfdir"])
+ .replace("@@VARIATION@@", variation)
)
config = open(defcfg).read()
config += "\n# Test Extra Configuration:\n"
config += open(extracfg).read()
+if varycfg:
+ config += "\n# Test Variation Configuration:\n"
+ config += open(varycfg).read()
+
cfg = expand(config)
open(out, "w").write(cfg)
import sys, os, json
-# Usage: mkcfg.py $OUT $NAME $CATEGORY $ENVS
-_, out, name, cat, envs = sys.argv
+# Usage: mkcfg.py $OUT $NAME $CATEGORY $ENVS $VARIATIONS
+_, out, name, cat, envs, variations = sys.argv
template = {
"name": name,
"category": cat,
"environments": envs.split(" "),
+ "variations": variations.split(" "),
}
open(out, "w").write(
-name="test-@@ENV@@-@@NAME@@"
+name="test-@@ENV@@-@@NAME@@@@VARIATION@@"
builder="hvm"
memory=128
firmware_override="@@XTFDIR@@/tests/@@NAME@@/test-@@ENV@@-@@NAME@@"
-name="test-@@ENV@@-@@NAME@@"
+name="test-@@ENV@@-@@NAME@@@@VARIATION@@"
loader="generic"
memory=128
kernel="@@XTFDIR@@/tests/@@NAME@@/test-@@ENV@@-@@NAME@@"
@section overview Overview
-A test has three important attributes.
+A test has several important attributes, and are often referred to in the form:
+ test-$ENVIRONMENT-$NAME[~$VARIATION]
+
+This identifies a single test instance, with the purpose of executing and
+providing a single result (See @ref running).
@subsection attr-name Name
-A tests name is unique identifier. The directory `tests/<name>/` contains all
-files related to a specific test.
+A tests name is a unique identifier. The directory `tests/$NAME/` contains
+all files related to a specific test. A test has one or more test instances.
@subsection attr-category Category
An individual test, compiled for more than one environment, will end up with a
individual microkernel binary for each specified environment.
+@subsection Variations
+
+Variations are optional. Some tests may only want to run the binary once, but
+some tests want to run the same binary multiple times in different
+configurations, reporting separate results.
+
+The variation suffix (if present) is arbitrary and defined by the test itself.
+
@section build Building
def __init__(self, arg):
""" Parse and verify 'arg' as a test instance. """
- self.env, self.name = parse_test_instance_string(arg)
+ self.env, self.name, self.variation = parse_test_instance_string(arg)
if self.env is None:
raise RunnerError("No environment for '%s'" % (arg, ))
+ if self.variation is None and get_all_test_info()[self.name].variations:
+ raise RunnerError("Test '%s' has variations, but none specified"
+ % (self.name, ))
+
def vm_name(self):
""" Return the VM name as `xl` expects it. """
return repr(self)
return path.join("tests", self.name, repr(self) + ".cfg")
def __repr__(self):
- return "test-%s-%s" % (self.env, self.name)
+ if not self.variation:
+ return "test-%s-%s" % (self.env, self.name)
+ else:
+ return "test-%s-%s~%s" % (self.env, self.name, self.variation)
def __hash__(self):
return hash(repr(self))
raise ValueError("Unknown environments '%s'" % (env, ))
self.envs = envs
- def all_instances(self, env_filter = None):
+ variations = test_json["variations"]
+ if not isinstance(variations, list):
+ raise TypeError("Expected list for 'variations', got '%s'"
+ % (type(variations), ))
+ self.variations = variations
+
+ def all_instances(self, env_filter = None, vary_filter = None):
"""Return a list of TestInstances, for each supported environment.
Optionally filtered by env_filter. May return an empty list if
the filter doesn't match any supported environment.
else:
envs = self.envs
- return [ TestInstance("test-%s-%s" % (env, self.name)) for env in envs ]
+ if vary_filter:
+ variations = set(vary_filter).intersection(self.variations)
+ else:
+ variations = self.variations
+
+ res = []
+ if variations:
+ for env in envs:
+ for vary in variations:
+ res.append(TestInstance("test-%s-%s~%s"
+ % (env, self.name, vary)))
+ else:
+ res = [ TestInstance("test-%s-%s" % (env, self.name))
+ for env in envs ]
+ return res
def __repr__(self):
return "TestInfo(%s)" % (self.name, )
def parse_test_instance_string(arg):
"""Parse a test instance string.
- Has the form: '[[test-]$ENV-]$NAME'
+ Has the form: '[[test-]$ENV-]$NAME[~$VARIATION]'
Optional 'test-' prefix
Optional $ENV environment part
Mandatory $NAME
+ Optional ~$VARIATION suffix
Verifies:
- $NAME is valid
- if $ENV, it is valid for $NAME
+ - if $VARIATION, it is valid for $NAME
- Returns: tuple($ENV or None, $NAME)
+ Returns: tuple($ENV or None, $NAME, $VARIATION or None)
"""
all_tests = get_all_test_info()
+ variation = None
+ if '~' in arg:
+ arg, variation = arg.split('~', 1)
+
parts = arg.split('-', 2)
parts_len = len(parts)
raise RunnerError("Test '%s' has no environment '%s'"
% (name, env))
- return env, name
+ # If a variation has been given, check it is valid
+ if variation is not None:
+ if not info.variations:
+ raise RunnerError("Test '%s' has no variations" % (name, ))
+ elif not variation in info.variations:
+ raise RunnerError("No variation '%s' for test '%s'"
+ % (variation, name))
+
+ return env, name, variation
# Cached test json from disk
# Second, sanity check others as full or partial test names
for arg in others:
- env, name = parse_test_instance_string(arg)
+ env, name, vary = parse_test_instance_string(arg)
instances = all_tests[name].all_instances(
env_filter = env and [env] or None,
+ vary_filter = vary and [vary] or None,
)
if not instances: