mirror of
https://gitlab.com/buildroot.org/buildroot.git
synced 2026-10-01 22:41:43 -09:00
support/testing: transfer config in environment instead of class variables
Passing runtime configuration to tests in BRConfigTest class variables
requires the "fork" start method for multiprocessing. With
"forkserver" (default for POSIX platforms since Python 3.14) class
variables modified in the parent process are lost, because the child
process does not inherit the full Python state. This broke the way
support/testing/run-tests sets configuration for BRConfigTest, so a
start method override was added in commit
3d2141bcee.
Instead this patch adds a settings dataclass (requires Python >= 3.7)
which is serialized into an environment variable from run-tests and
read during BRConfigTest.__init__(). Reading is skipped if the
environment variable is not set so test discovery (not execution)
works without configuration (e.g. utils/get-developers uses this).
With that, run-tests no longer relies on a specific multiprocessing
start method.
Signed-off-by: Fiona Klute <fiona.klute@gmx.de>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
This commit is contained in:
committed by
Thomas Petazzoni
parent
3872cf8a75
commit
10088b5381
@@ -1,6 +1,9 @@
|
||||
import unittest
|
||||
import os
|
||||
import dataclasses
|
||||
import datetime
|
||||
import json
|
||||
from typing import ClassVar
|
||||
|
||||
from infra.builder import Builder
|
||||
from infra.emulator import Emulator
|
||||
@@ -22,6 +25,33 @@ MINIMAL_CONFIG = \
|
||||
"""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class BRTestSettings:
|
||||
"""support/testing/run-tests stores runtime settings in this
|
||||
object, and serializes it into the environment variable named in
|
||||
CONF_ENV. On instantiation, BRConfigTest loads this
|
||||
configuration. That way configuration can be transferred into
|
||||
multiprocessing worker processes without requiring the "fork"
|
||||
start method.
|
||||
|
||||
"""
|
||||
CONF_ENV: ClassVar[str] = 'BR_TEST_CONFIG_JSON'
|
||||
|
||||
downloaddir: str
|
||||
outputdir: str
|
||||
logtofile: bool = True
|
||||
keepbuilds: bool = False
|
||||
jlevel: int = 0
|
||||
timeout_multiplier: int = 1
|
||||
|
||||
def to_env(self) -> None:
|
||||
os.environ[self.CONF_ENV] = json.dumps(dataclasses.asdict(self))
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> 'BRTestSettings':
|
||||
return cls(**json.loads(os.environ[cls.CONF_ENV]))
|
||||
|
||||
|
||||
class BRConfigTest(unittest.TestCase):
|
||||
"""Test up to the configure stage."""
|
||||
config: str
|
||||
@@ -35,6 +65,8 @@ class BRConfigTest(unittest.TestCase):
|
||||
|
||||
def __init__(self, names):
|
||||
super(BRConfigTest, self).__init__(names)
|
||||
if BRTestSettings.CONF_ENV in os.environ:
|
||||
self.conf_from_env()
|
||||
self.testname = self.__class__.__name__
|
||||
self.builddir = self.outputdir and os.path.join(self.outputdir, self.testname)
|
||||
self.config += '\nBR2_DL_DIR="{}"\n'.format(self.downloaddir)
|
||||
@@ -44,6 +76,11 @@ class BRConfigTest(unittest.TestCase):
|
||||
print("{} {:40s} {}".format(datetime.datetime.now().strftime("%H:%M:%S"),
|
||||
self.testname, msg))
|
||||
|
||||
def conf_from_env(self) -> None:
|
||||
conf = BRTestSettings.from_env()
|
||||
for f in dataclasses.fields(conf):
|
||||
setattr(self, f.name, getattr(conf, f.name))
|
||||
|
||||
def setUp(self):
|
||||
self.show_msg("Starting")
|
||||
self.b = Builder(self.config, self.builddir, self.logtofile, self.jlevel)
|
||||
|
||||
@@ -6,7 +6,7 @@ import sys
|
||||
|
||||
import nose2
|
||||
|
||||
from infra.basetest import BRConfigTest
|
||||
from infra.basetest import BRTestSettings
|
||||
|
||||
import infra
|
||||
|
||||
@@ -44,9 +44,6 @@ def main():
|
||||
script_path = os.path.realpath(__file__)
|
||||
test_dir = os.path.dirname(script_path)
|
||||
|
||||
if args.stdout:
|
||||
BRConfigTest.logtofile = False
|
||||
|
||||
if args.list:
|
||||
print("List of tests")
|
||||
nose2_args = [
|
||||
@@ -69,7 +66,7 @@ def main():
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
BRConfigTest.downloaddir = os.path.abspath(args.download)
|
||||
args.download = os.path.abspath(args.download)
|
||||
|
||||
if args.prepare_only:
|
||||
emulator_builtin_binaries = ["kernel-vexpress-5.10.202",
|
||||
@@ -78,7 +75,7 @@ def main():
|
||||
"versatile-pb-5.10.202.dtb"]
|
||||
print("Downloading emulator builtin binaries")
|
||||
for binary in emulator_builtin_binaries:
|
||||
infra.download(BRConfigTest.downloaddir, binary)
|
||||
infra.download(args.download, binary)
|
||||
return 0
|
||||
|
||||
if args.output is None:
|
||||
@@ -90,15 +87,13 @@ def main():
|
||||
if not os.path.exists(args.output):
|
||||
os.mkdir(args.output)
|
||||
|
||||
BRConfigTest.outputdir = os.path.abspath(args.output)
|
||||
|
||||
if args.all is False and not args.testname:
|
||||
print("No test selected")
|
||||
print("")
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
BRConfigTest.keepbuilds = args.keep
|
||||
jlevel = 0
|
||||
|
||||
if args.testcases != 1:
|
||||
if args.testcases < 1:
|
||||
@@ -109,7 +104,7 @@ def main():
|
||||
# same default BR2_JLEVEL as package/Makefile.in
|
||||
br2_jlevel = 1 + multiprocessing.cpu_count()
|
||||
each_testcase = int((br2_jlevel + args.testcases) / args.testcases)
|
||||
BRConfigTest.jlevel = each_testcase
|
||||
jlevel = each_testcase
|
||||
|
||||
if args.jlevel:
|
||||
if args.jlevel < 0:
|
||||
@@ -118,14 +113,22 @@ def main():
|
||||
parser.print_help()
|
||||
return 1
|
||||
# the user can override the auto calculated value
|
||||
BRConfigTest.jlevel = args.jlevel
|
||||
jlevel = args.jlevel
|
||||
|
||||
if args.timeout_multiplier < 1:
|
||||
print("Invalid multiplier for timeout values")
|
||||
print("")
|
||||
parser.print_help()
|
||||
return 1
|
||||
BRConfigTest.timeout_multiplier = args.timeout_multiplier
|
||||
|
||||
BRTestSettings(
|
||||
logtofile=not args.stdout,
|
||||
downloaddir=args.download,
|
||||
outputdir=os.path.abspath(args.output),
|
||||
keepbuilds=args.keep,
|
||||
jlevel=jlevel,
|
||||
timeout_multiplier=args.timeout_multiplier
|
||||
).to_env()
|
||||
|
||||
nose2_args = ["-v",
|
||||
"-N", str(args.testcases),
|
||||
@@ -142,15 +145,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# python 3.14 changed default start method from fork to
|
||||
# fork-server, which is not compatible with the nose2 setup
|
||||
if multiprocessing.get_start_method(allow_none=True) != "fork":
|
||||
multiprocessing.set_start_method("fork", force=True)
|
||||
# set_start_method throws a RuntimeError if called more than
|
||||
# once.
|
||||
# Debian python3-nose2 0.15.1-2 includes a patch adding
|
||||
# another set_start_method() call, so monkey patch it out to
|
||||
# get rid of this
|
||||
multiprocessing.set_start_method = lambda *args: None
|
||||
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user