First commit

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2022-11-01 13:28:53 +01:00
commit 4b912741cb
63 changed files with 3753 additions and 0 deletions

4
src/Makefile Normal file
View file

@ -0,0 +1,4 @@
TOPDIR = ..
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/dirs.mk

4
src/python/Makefile Normal file
View file

@ -0,0 +1,4 @@
TOPDIR = ../..
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/py-mods.mk

View file

@ -0,0 +1,4 @@
TOPDIR = ../../..
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/py-mod.mk

View file

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
import asyncio
import sys
import traceback
import jwutils
from jwutils.log import *
from .Machine import Machine
from .MachineCmd import MachineCmd
from .TestPhases import TestPhases
from .TestCases import TestCases
from .TestResults import TestResults
class CmdTestOs(MachineCmd): # export
def __init__(self):
super().__init__('test-os', help='Run batches of tests against a machine')
self.__results = TestResults()
# -- phase trigger methods
async def __trigger_boot(self, env, machine):
slog(INFO, "requesting power-on")
await machine.request_power_on(env)
async def __trigger_up(self, env, machine):
await machine.wait_up(env)
async def __trigger_shutdown(self, env, machine):
await machine.request_shutdown(env)
async def __trigger_post(self, env, machine):
await machine.wait_poweroff(env)
# -- phase filtered batches
async def __establish_connections(self, phase, env, machine):
for conn in env.connections:
if not phase in conn.info.phases:
if conn.instance is None:
continue
slog(INFO, 'Phase "{}": closing connection "{}" with phases [{}]'.format(phase.name, conn.info, conn.info.phases_str))
await machine.unregister_connection(conn.instance)
conn.instance = None
continue
slog(DEBUG, " phase matches")
if conn.instance is not None:
continue
slog(INFO, 'Phase "{}": opening connection "{}" with phases [{}]'.format(phase.name, conn.info, conn.info.phases_str))
conn.instance = machine.register_connection(env, conn.info)
async def __run_test_cases(self, phase, env, machine, cases):
connections = [ c.instance for c in env.connections if c.instance ]
for case in cases:
if not phase in case.phases:
continue
delim = '--- running test case "{}"'.format(case)
prio = NOTICE
prefix = ' {}|{}'.format(*console_color_chars(prio))
slog(prio, ',' + delim)
jwutils.log.append_to_prefix(prefix)
try:
if not machine.clear_for_tests():
raise Exception("machine is not clear for running tests")
rr = await case.run(env, machine, phase)
if rr:
slog(ERR, "FAIL: ", rr)
except Exception:
rr = sys.exc_info()[1]
slog(ERR, "FAIL: ", rr)
slog_m(ERR, traceback.format_exc())
self.__results.add(case, phase, rr)
jwutils.log.remove_from_prefix(len(prefix))
slog(prio, '`' + delim)
@classmethod
def __get_trigger(cls, phase):
return getattr(cls, '_{}__trigger_{}'.format(cls.__name__, phase.name.lower()), None)
# override MachineCmd._run()
async def _run(self, env):
machine = None
try:
machine = await Machine.create(env)
test_cases = TestCases(env.args.test_case_path, dummies=env.args.dummy_tests)
for phase in TestPhases.Phase:
if not machine.clear_for_tests():
raise Exception("machine is not clear for running tests")
delim = '-' * 60 + " Phase {} -- ".format(phase.name)
prio = NOTICE
prefix = ' {}|{}'.format(*console_color_chars(prio))
slog(prio, ',' + delim + '>')
jwutils.log.append_to_prefix(prefix)
try:
trigger_method = self.__get_trigger(phase)
if trigger_method is not None:
await trigger_method(phase, env, machine)
await self.__establish_connections(phase, env, machine)
await self.__run_test_cases(phase, env, machine, test_cases)
finally:
jwutils.log.remove_from_prefix(len(prefix))
slog(prio, '`' + delim + '<')
except Exception as e:
slog(ERR, "failed to run tests against machine {}: {}".format(machine, e))
slog_m(ERR, traceback.format_exc())
raise
finally:
if machine is not None:
await machine.request_shutdown(env)
await machine.wait_poweroff(env)
await machine.cleanup(env)
for conn in env.connections:
if conn.instance:
await machine.unregister_connection(conn.instance)
conn.instance = None
del machine
self.__results.dump(env.args)
n_failed = self.__results.n_failed()
if (n_failed):
slog(ERR, "%d test%s failed" % (n_failed, "" if n_failed == 1 else "s"))
return -1
return 0
def add_parser(self, parsers):
p = super().add_parser(parsers)
self.__results.add_arguments(p)
return p

View file

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
from abc import ABC, abstractmethod
from enum import Enum, IntFlag
import asyncio
import types
from jwutils.log import *
from .TestPhases import TestPhases
# TODO: Make this a valid abstract python stream
class Connection(ABC): # export
class Proto(Enum):
Serial = 1
Console = 2
CAN = 3
HTTP = 4
Fifos = 5
class Flags(IntFlag):
FailOnTimeout = 0x01
ReadStripNewline = 0x02
ReadDecodeToString = 0x04
class Info:
@staticmethod
def _parse_attr_proto(rhs):
if not rhs in Connection.Proto._member_names_:
raise Exception("Unknown connection protocol >" + rhs + "<")
return Connection.Proto[rhs]
@staticmethod
def _parse_attr_phases(rhs):
return TestPhases.parse(rhs)
def __getitem__(self, key_):
key, default = (key_[0], key_[1]) if isinstance(key_, tuple) else (key_, None)
if key in self.__spec.keys():
return self.__spec[key]
return default
#p.add_argument("-C", "--connection", help="Connection specification of the form 'proto=console,phase=pre|up-shutdown'", action='append', default=[])
def __init(self, spec):
self.spec = spec
self.phases = set()
self.proto = None
tokens = spec.split(',')
if not len(tokens):
raise Exeption("Empty connection spec")
if tokens[0].find("=") == -1:
tokens[0] = 'proto=' + tokens[0]
self.__spec = {}
for tok in tokens:
assignment = tok.split('=')
lhs = assignment[0]
rhs = assignment[1] if len(assignment) > 1 else True
func = getattr(Connection.Info, '_parse_attr_' + lhs, None)
if not isinstance(func, types.FunctionType):
#setattr(self, lhs, rhs) # FIXME: This is overkill
self.__spec[lhs] = rhs
else:
setattr(self, lhs, func(rhs))
self.__spec[lhs] = func(rhs)
def __init__(self, spec):
self.__init(spec)
def __format__(self, fmt):
return self.spec
@staticmethod
def parse_spec(spec):
return ConInfo(spec)
@property
def phases_names(self):
return [p.name for p in self.phases]
@property
def phases_str(self):
return ', '.join(self.phases_names)
def __init__(self, env, spec):
self.env = env
if isinstance(spec, self.Info):
self.__info = spec
elif isinstance(spec, str):
self.__info = Connection.Info(spec)
else:
raise TypeError('Invalid type "{}" of spec "{}", should be string.'.format(type(spec), spec))
def __format__(self, fmt):
return self.__info.spec
@abstractmethod
async def _read(self, act_timeout, flags):
return await self._read(act_timeout, flags)
@abstractmethod
async def _readline(self, timeout):
pass
@abstractmethod
async def _write(self, data):
pass
@abstractmethod
async def _close(self):
pass
@property
def info(self):
return self.__info
@info.setter
def info(self, value):
raise Exception("Tried to set immutable attribute info")
async def read(self, act_timeout=None, flags=Flags.ReadDecodeToString):
r = await self._read(act_timeout, flags)
if r is not None:
if flags & self.Flags.ReadDecodeToString:
r = r.decode('utf-8')
if flags & self.Flags.ReadStripNewline:
if len(r) and r[-1] == '\n':
r = r[0:-1]
return r
async def readline(self, timeout=None):
return await self._readline(timeout)
async def write(self, data):
return await self._write(data)
async def close(self):
return await self._close()

View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
from .Connection import Connection
class Connections: # export
class Instance:
def __init__(self, info, conn = None):
self.info = info
self.instance = conn
def __init__(self, rhs=None):
if rhs:
self.__contents = rhs.__contents
else:
self.__contents = []
self.__idx = 0
def __iter__(self):
return self
def __next__(self):
self.__idx += 1
try:
return self.__contents[self.__idx-1]
except IndexError:
self.__idx = 0
raise StopIteration
def __len__(self):
return len(self.__contents)
def append(self, stuff):
if isinstance(stuff, Connection.Info):
self.__contents.append(Connections.Instance(stuff, None))
elif isinstance(stuff, Connection):
self.__contents.append(Connections.Instance(stuff.info, stuff))
elif isinstance(stuff, Connections.Instance):
self.__contents.append(stuff)
elif isinstance(stuff, str):
self.__contents.append(Connections.Instance(Connection.Info(stuff), None))
else:
raise Exception("tried to append connection of incompatible type ", stuff)

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from .Connections import Connections
from jwutils import Options
class Environment:
def __init__(self, cmd, args):
self.args = args
self.cmd = cmd
self.connections = Connections()
self.features = Options(args.features)
for spec in args.connection:
self.connections.append(spec)
@property
def eloop(self):
return self.cmd.cmds.eloop

View file

@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
import os
import sys
import re
from abc import ABC, abstractmethod
from enum import Enum
import tempfile
import shutil
import asyncio
import importlib
from jwutils.log import *
from jwutils.misc import *
from .Connection import Connection
from .conn.Fifos import Fifos as ConnFifos
class Machine(ABC): # export
def __init__(self, env):
# -- readonly stuff
self.__env = env
self.__name = env.args.platform + '@' + env.args.backend
self.__exe_path = None
self.__tmpdir = None
self.__connections = {}
def __format__(self, fmt):
return self.__name
def _register_connection(self, conn):
if conn.info.spec in self.__connections.keys():
raise Exception("Connection {} is already up".format(conn))
self.__connections[conn.info.spec] = conn
async def _unregister_connection(self, conn):
spec = conn.info.spec
if spec in self.__connections.keys():
del self.__connections[conn.info.spec]
await conn.close()
@classmethod
async def create(cls, env):
backend = env.args.backend
if backend.find('.') == -1:
backend = 'devtest.os.be.' + backend
slog(NOTICE, "loading machine ", backend)
mod = importlib.import_module(backend)
r = mod.Machine(env)
await r.init()
return r
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
self.__name = value
@property
def env(self):
return self.__env
@property
def exe_path(self):
if not self.__exe_path:
self.__exe_path = os.path.abspath(sys.argv[0])
return self.__exe_path
@property
def exe_basename(self):
return re.sub('\.py$', '', os.path.basename(self.exe_path))
@property
def tmpdir(self):
if self.env.args.persistent_tmpdir:
if not self.__tmpdir:
self.__tmpdir = self.env.args.persistent_tmpdir
shutil.rmtree(self.__tmpdir, ignore_errors=True)
os.mkdir(self.__tmpdir)
return self.__tmpdir
if not self.__tmpdir:
self.__tmpdir = tempfile.TemporaryDirectory(prefix=self.exe_basename+'-')
return self.__tmpdir.name
@abstractmethod
async def init(self):
pass
async def cleanup(self, env):
pass
def clear_for_tests(self):
return True
def register_connection(self, env, info):
slog(NOTICE, 'opening connection "{}" to platform "{}"'.format(info, self.env.args.platform))
try:
mod = importlib.import_module('devtest.os.conn.' + info.proto.name) # untested
classes = get_derived_classes(mod, Connection)
if not len(classes):
raise Exception("Tried to establish connection {} of unsupported type {}".format(info, info.proto))
slog(DEBUG, 'constructing connection with info "{}"'.format(info))
r = classes[0](env, info)
slog(DEBUG, "found connection definition class >%s<" % type(r))
self._register_connection(r)
except Exception as e:
slog(ERR, "failed to open connection {}: {}".format(info, e))
raise
return r
async def unregister_connection(self, conn):
await self._unregister_connection(conn)
await conn.close()
@abstractmethod
async def request_power_on(self):
pass
async def wait_up(self, timeout):
return True
@abstractmethod
async def request_shutdown(self):
pass
async def wait_poweroff(self, timeout):
return True

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod
import jwutils
from jwutils.log import *
from .Environment import Environment
class MachineCmd(jwutils.Cmd): # export
def __init__(self, name, help):
super().__init__(name, help=help)
def add_parser(self, parsers):
p = super().add_parser(parsers)
p.add_argument("-B", "--backend", help="Machine backend", default='qemu')
p.add_argument("-P", "--platform", help="Target platform type", required=True)
p.add_argument("-C", "--connection", help="Connection specification of the form 'proto=console,phase=pre|up-shutdown'", action='append', default=[])
p.add_argument("-T", "--test-case-path", help="Test case search path, directories separated by colons", required=True)
p.add_argument( "--shutdown-timeout", help="Timeout for a machine to complete an ACPI shutdown request in ms", default=60000)
p.add_argument( "--poweroff-timeout", help="Timeout for a machine to complete a poweroff request in ms", default=100)
p.add_argument( "--features", help="Comma-separated list of features that are passed to the test cases for interpretation", default='')
# TODO: Don't pollute list of generic machine options with Qemu specific stuff
p.add_argument("-R", "--root-part-number", help="Number of root partition (starting from 1)")
p.add_argument("--medium", help="Boot medium (file system path or \"net\")")
p.add_argument( "--persistent-tmpdir", help="Temporary directory for storing runtime data and scripts, by default a random one is created", default=None)
p.add_argument( "--dummy-tests", help="Add hard-coded dummy test cases to the test run", action='store_true', default=False)
# TODO: Don't pollute list of generic machine options with Qemu specific stuff
# TODO: Remove short options, currently only there for qemu.sh compatibility
p.add_argument("-S", "--qe-network-script", help="Script to be called for network bringup / teardown, builtin version is used by default", default=None)
p.add_argument("-k", "--qe-kernel", help="Kernel for Qemu to use, none by default", default=None)
p.add_argument("-D", "--qe-dtb", help="Device-tree blob for the kernel to use, requires --qe-kernel, none by default", default=None)
p.add_argument("-o", "--qe-opts", help="Additional arguments to Qemu", default=None)
return p
@abstractmethod
async def _run(self, env):
pass
async def run(self, args):
env = Environment(self, args)
return await self._run(env)

View file

@ -0,0 +1,4 @@
TOPDIR = ../../../..
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/py-mod.mk

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from jwutils.log import *
from .TestCase import TestCase
class TcDummy(TestCase):
@property
def is_dummy(self):
return True
async def _run(self, env, machine, phase):
slog(NOTICE, "running dummy test case in phase >%s<" % phase.name)
#return None
# return an error message string if this test fails
return "Stumbled over chair"

View file

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod, ABC
from jwutils.misc import *
from .TestPhases import TestPhases
# could be derived from Python unittest.TestCase, I currently don't see the
# benefit, though
class TestCase: # export
def __init__(self):
self.__name = object_builtin_name(self)
self.__factory = None
self.__phases = { TestPhases.Phase.Up }
def __format__(self, fmt):
return self.name
@abstractmethod
async def _run(self, env, machine, phase):
pass
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
self.__name = value
@property
def factory(self):
return self.__factory
@factory.setter
def factory(self, value):
self.__factory = value
@property
def phases(self):
return self.__phases
@phases.setter
def phases(self, value):
self.__phases = value
@property
def is_dummy(self):
return False
async def run(self, env, machine, phase):
return await self._run(env, machine, phase)

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod, ABC
class TestCaseFactory(ABC): # export
def __init__(self, path):
self.path = path
@abstractmethod
def test_cases(self):
pass

View file

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod, ABC
import sys
import re
import inspect
from jwutils.log import *
from jwutils.misc import *
from .TestCase import TestCase
from .TestCaseFactory import TestCaseFactory
from .tcf import *
class TestCases: # export
def __init__(self, path, dummies=False):
self.__path = path.split(':')
self.__dummies = dummies
self.__idx = 0
self.__cases = []
factories = set()
# use list() to avoid expansion of sys.modules during iteration
for name, mod in list(sys.modules.items()):
members = inspect.getmembers(mod, inspect.isclass)
for name, c in members:
if inspect.isabstract(c):
continue
if TestCaseFactory in inspect.getmro(c):
factories.add(c)
continue
slog(NOTICE, "using test case factories:", ', '.join([f.__name__ for f in factories]))
for factory in factories:
slog(INFO, "checking test case factory", str(factory))
cases = factory(self.__path).test_cases()
for c in cases:
if c.is_dummy and not self.__dummies:
continue
c.factory = factory
self.__cases.append(c)
# -- fancy log found test cases
delim = "-" * 60 + " found test cases"
fmt = "| {:>30} | {:<10} | {}"
slog(NOTICE, "," + delim + " >")
slog(NOTICE, fmt.format('Factory', 'Phases', 'Test Case'))
slog(NOTICE, "+" + re.sub('[^-]', '-', delim) + "--")
for c in self.__cases:
slog(NOTICE, fmt.format(c.factory.__name__, ', '.join([p.name for p in c.phases]), c.name))
slog(NOTICE, "`" + delim + " <")
def __iter__(self):
return self
def __next__(self):
self.__idx += 1
try:
return self.__cases[self.__idx-1]
except IndexError:
self.__idx = 0
raise StopIteration # Done iterating.

View file

@ -0,0 +1,39 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from enum import IntEnum
import itertools
class TestPhases: # export
class Phase(IntEnum):
Pre = 1
Boot = 2
Up = 3
Shutdown = 4
Post = 5
@staticmethod
def __parse(st):
return TestPhases.Phase[st.capitalize()]
@staticmethod
def parse(spec):
r = set()
terms = spec.split('|')
for t in terms:
interval = t.split('-')
if len(interval) == 1:
r.add(TestPhases.__parse(interval[0]))
continue
for p in itertools.islice(
TestPhases.Phase,
TestPhases.__parse(interval[0])-1,
TestPhases.__parse(interval[1])
):
r.add(p)
return r
if __name__ == '__main__':
for p in [ 'Pre-Post', 'Pre', 'Boot', 'Shutdown-Post', 'Blah' ]:
print("{} = {}".format(p, TestPhases.parse(p)))

View file

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
from enum import IntEnum
from termcolor import colored
from texttable import Texttable
from jwutils.log import *
class TestResults: # export
class Field(IntEnum):
Case = 0
Phase = 1
Result = 2
def __init__(self):
self.__deco = {
'border': Texttable.BORDER,
'header': Texttable.HEADER,
'hlines': Texttable.HLINES,
'vlines': Texttable.VLINES
}
self.__cols = {
'Case': {
'bits': set(['case']),
'fmt': self.__fmt_case
},
'Phase': {
'bits': set(['phase']),
'fmt': self.__fmt_phase
},
'Result': {
'bits': set(['result']),
'fmt': self.__fmt_result
},
'Message': {
'bits': set(['msg']),
'fmt': self.__fmt_msg
},
}
self.__col_aliases = {
'all': [
'Case',
'Phase',
'Result',
'Message',
]
}
self.__records = []
def ___cols(self, args):
cols = args.result_cols
if cols in self.__col_aliases:
return self.__col_aliases[cols]
return [c.strip(' ') for c in cols.split(',')]
def ___records(self, args):
# hook to apply filters defined in args
return self.__records
def __fmt_case(self, record):
return record[TestResults.Field.Case].name
def __fmt_phase(self, record):
return record[TestResults.Field.Phase].name
def __fmt_result(self, record):
rr = record[TestResults.Field.Result]
if rr is None:
prio = NOTICE
color = 'green'
r = "PASS"
else:
prio = ERR
color = 'red'
r = "FAIL"
return r
# currently disabled: Texttable doesn't support colored cell contents
#c_on, c_off = console_color_chars(prio)
#return c_on + r + c_off
#return colored(r, color)
def __fmt_msg(self, record):
rr = record[TestResults.Field.Result]
if rr is None:
return ""
return rr
def add_arguments(self, p):
p.add_argument("--result-cols", help="Comma-separated list of output columns, " +
"possible columns are: " + ', '.join(self.__cols.keys()), default="all")
p.add_argument("--result-deco", help="Comma-separated list of output table decoration flags, " +
"possible values are: " + ', '.join(self.__deco.keys()), default="all")
return p
def add(self, case, phase, result):
self.__records.append([case, phase, result])
def n_failed(self):
return len(list(filter(None, [rec[TestResults.Field.Result] for rec in self.__records])))
def dump(self, args):
t = Texttable(max_width=160)
# -- deco
deco_keys = args.result_deco
if deco_keys == 'all':
deco_keys = self.__deco.keys()
else:
deco_keys = filter(None, args.deco.split(','))
deco = 0x0
for d in deco_keys:
deco |= self.__deco[d]
t.set_deco(deco)
cols = self.___cols(args)
#slog(DEBUG, 'Columns: ' + ' '.join(cols))
records = self.___records(args)
bits = set()
for c in cols:
bits |= self.__cols[c]['bits']
#info = dict()
#out = dict()
rows = []
row = []
if 'header' in deco_keys:
for c in cols:
row.append(c)
rows.append(row)
#slog(DEBUG, 'Header: ' + ' '.join(row))
for record in records:
row = []
for c in cols:
row.append(self.__cols[c]['fmt'](record))
#slog(DEBUG, 'case-row: ' + ' '.join(row))
rows.append(row)
#slog(DEBUG, rows)
t.add_rows(rows)
print(t.draw())

View file

@ -0,0 +1,4 @@
TOPDIR = ../../../../..
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/py-mod.mk

View file

@ -0,0 +1,235 @@
# -*- coding: utf-8 -*-
import os
from abc import ABC
from abc import abstractmethod
import shutil
from jwutils.log import *
from ...Connection import Connection
class Invocation(ABC):
# -- private stuff
def __init__(self, machine):
self.__cl = None
self.__cf = ""
self.__mach = machine
@property
def mach(self):
return self.__mach
@property
def env(self):
return self.__mach.env
def __val(self, func_name, defval=None):
func_name = '_impl_' + func_name
method = getattr(self, func_name, None)
if not method:
if defval is not None:
return defval
raise Exception("tried to invoke unknown method >%s<" % func_name)
return method()
def __do_add_cl(self, stuff):
if isinstance(stuff, list):
self.__cl.extend(stuff)
else:
self.__cl.append(stuff)
def __add_cl(self, func_name, opt_name=None):
val = self.__val(func_name)
if not val:
return
arr = []
if opt_name:
arr.append(opt_name)
if isinstance(val, list):
arr.extend(val)
else:
arr.append(val)
self.__do_add_cl(arr)
def __add_cf(self, func_name, key):
val = self.__val(func_name)
if not val:
return
self.__cf += '{}="{}"\n'.format(key, val)
# -- protected API methods
#@abstractmethod
def _impl_brctl_exe(self):
r = shutil.which("brctl", path="/bin:/usr/bin:/sbin:/usr/sbin")
if r is None:
raise Exception("no brctl executable found")
return r
#@abstractmethod
def _impl_bridge_name(self):
return "brdev0"
#@abstractmethod
def _impl_bridge_net(self):
return "192.168.100.0/24"
#@abstractmethod
def _impl_bridge_addr(self):
return "192.168.100.1"
#@abstractmethod
def _impl_mac_addr(self):
return "00:0B:DC:9B:D6:DA"
#@abstractmethod
def _impl_nic_model(self):
pass
@abstractmethod
def _impl_qemu_exe(self):
pass
@abstractmethod
def _impl_machine(self):
pass
#@abstractmethod
def _impl_memory(self):
return "1G"
#@abstractmethod
def _impl_opts_interactive(self):
console = False
r = []
for conn in self.env.connections:
if conn.info.proto == Connection.Proto.Console:
slog(INFO, "found console: {}".format(conn.info))
#r.extend(['-serial pipe:/tmp/qemu-{}-console'.format(self.env.args.platform), '-display none'])
r.extend([
'-chardev', 'pipe,id=cpipe,path={},delay=off,wait=off,mux=off'.format(self.mach.fifo_base("console")),
'-serial', 'chardev:cpipe',
'-display', 'none'
])
console = True
r.extend([
'-chardev', 'pipe,id=cmon,path={},delay=off,wait=off'.format(self.mach.fifo_base("monitor")),
'-mon', 'chardev=cmon,mode=readline',
#'-mon', 'chardev=cmon,mode=control,pretty=off',
#'-monitor', 'stdio',
'-S', # start with CPU stopped, need "cont" command on monitor to start
])
if not console:
r.extend(["-nographic"])
return r
#@abstractmethod
def _impl_kernel(self):
if self.env.args.qe_kernel:
return self.env.args.qe_kernel
return None
#@abstractmethod
def _impl_qemu_opts(self):
if self.env.args.qe_opts:
slog(NOTICE, "qemu_opts=>" + self.env.args.qe_opts + "<")
#os.exit(1)
return self.env.args.qe_opts.split()
return None
#@abstractmethod
def _impl_dtb(self):
if self.env.args.qe_dtb:
return self.env.args.qe_dtb
return None
#@abstractmethod
def _impl_append(self):
pass
def _impl_append_wrapper(self):
r = self._impl_append()
ct = "devtest.qemu=1"
if not r or not len(r):
return r
if isinstance(r, list):
r.append(ct)
return r
return r + ' ' + ct
##@abstractmethod
def _impl_audio_dev(self):
return "none,id=blah"
#@abstractmethod
def _impl_disks(self):
pass
# -- public API
def typename(self):
return self.__class__.__name__
@classmethod
def create(cls, platform):
mod_name = "Invocation_" + platform
mod = importlib.import_module("devtest.os.be.qemu." + mod_name)
@property
def cmdline(self):
if self.__cl is None:
self.__cl = []
self.__do_add_cl([shutil.which("sudo"), "-E"])
self.__add_cl('qemu_exe')
self.__add_cl('opts_interactive')
self.__add_cl('machine', '-M')
self.__add_cl('kernel', '-kernel')
self.__add_cl('append_wrapper', '-append')
self.__add_cl('dtb', '-dtb')
self.__add_cl('memory', '-m')
self.__add_cl('audio_dev', '-audiodev')
self.__do_add_cl(['-rtc', 'base=utc'])
self.__add_cl('disks')
nic="nic"
val = self.__val('nic_model', '')
if val is not None and len(val):
nic += ',model=' + val
val = self.__val('mac_addr', '')
if val is not None and len(val):
nic += ',macaddr=' + val
self.__do_add_cl(["-net", nic, "-net", "tap,id=blah0,ifname=tap11"
+ ",script=" + self.__mach.net_helper_path(True)
+ ",downscript=" + self.__mach.net_helper_path(False)
])
self.__add_cl('qemu_opts')
return self.__cl
@property
def helper_config(self):
if not self.__cf:
keys = [
"append",
#"base",
"brctl_exe",
"bridge_addr",
"bridge_name",
"bridge_net",
#"dns_domain",
#"hostname",
#"short_hostname",
"kernel",
"dtb",
"mac_addr",
"machine",
#"nic_model",
"qemu_exe",
"qemu_opts",
#"root_dev",
#"root_part_number",
#"dev_platform"
]
for k in keys:
self.__add_cf(k, k)
return self.__cf

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod
import shutil
from .Invocation import Invocation
class Invocation_q3beige(Invocation): # export
def __init__(self, machine):
super().__init__(machine)
self.__qemu_exe = shutil.which("qemu-system-ppc")
def _impl_qemu_exe(self):
return self.__qemu_exe
def _impl_machine(self):
return "g3beige"
def _impl_append(self):
return "console=ttyS0 root=/dev/hda2"
def _impl_disks(self):
return ["-hda", self.mach.env.args.medium]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod
import shutil
from .Invocation import Invocation
class Invocation_sabrelite(Invocation): # export
def __init__(self, machine):
super().__init__(machine)
self.__qemu_exe = shutil.which("qemu-system-arm")
def _impl_qemu_exe(self):
return self.__qemu_exe
def _impl_machine(self):
return "sabrelite"
def _impl_append(self):
return "root=/dev/mmcblk3p2 console=ttymxc0,115200 nomodeset vga=current video=vesafb:off locale.LANG=C locale.LANGUAGE=C locale.LC_MESSAGES=C locale.LC_CTYPE=C TERM=dumb"
def _impl_disks(self):
return ["-drive", "if=none,id=mydrive,file={},format=raw".format(self.mach.env.args.medium), "-device", "sd-card,id=sd0,drive=mydrive"]

Some files were not shown because too many files have changed in this diff Show more