1. rename test suite methods for better intuition.
2. add description for suite and case.
3. other minor changes
This commit is contained in:
Chi Song 2020-08-07 10:27:15 +08:00
Родитель 39458d1a9d
Коммит 079e61c21d
22 изменённых файлов: 145 добавлений и 120 удалений

Просмотреть файл

@ -0,0 +1,51 @@
from lisa import CaseMetadata, SuiteMetadata
from lisa.core.testSuite import TestSuite
from lisa.util.logger import log
@SuiteMetadata(
area="demo",
category="simple",
description="""
this is an example test suite.
it helps to understand how to write a test case.
""",
tags=["demo"],
)
class HelloWorld(TestSuite):
@CaseMetadata(
description="""
this test case use default node to start a procecss to echo hello world.
""",
priority=1,
)
def hello(self) -> None:
log.info(f"node count: {len(self.environment.nodes)}")
default_node = self.environment.defaultNode
result = default_node.execute("echo hello world!")
log.info(f"stdout of node: '{result.stdout}'")
log.info(f"stderr of node: '{result.stderr}'")
log.info(f"exitCode of node: '{result.exitCode}'")
log.info("try me on a remote node, same code!")
@CaseMetadata(
description="""
do nothing, show how caseSetup, suiteSetup works.
""",
priority=2,
)
def bye(self) -> None:
log.info("bye!")
def beforeSuite(self) -> None:
log.info("setup my test suite")
log.info(f"see my code at {__file__}")
def afterSuite(self) -> None:
log.info("clean up my test suite")
def beforeCase(self) -> None:
log.info("before test case")
def afterCase(self) -> None:
log.info("after test case")

Просмотреть файл

@ -1,33 +0,0 @@
from lisa import CaseMetadata, SuiteMetadata
from lisa.core.testSuite import TestSuite
from lisa.util.logger import log
@SuiteMetadata(area="demo", category="simple", tags=["demo"])
class SimpleTestSuite(TestSuite):
@CaseMetadata(priority=1)
def hello(self) -> None:
log.info("environment: %s", len(self.environment.nodes))
default_node = self.environment.defaultNode
result = default_node.execute("echo hello world!")
log.info("stdout of node: '%s'", result.stdout)
log.info("stderr of node: '%s'", result.stderr)
log.info("exitCode of node: '%s'", result.exitCode)
log.info("try me on a remote node, same code!")
@CaseMetadata(priority=1)
def bye(self) -> None:
log.info("bye!")
def caseSetup(self) -> None:
log.info("setup my test suite")
log.info("see my code at %s", __file__)
def caseCleanup(self) -> None:
log.info("clean up my test suite")
def beforeCase(self) -> None:
log.info("before test case")
def afterCase(self) -> None:
log.info("after test case")

Просмотреть файл

@ -77,17 +77,14 @@ def list_start(args: Namespace) -> None:
factory = TestFactory()
for metadata in factory.cases.values():
log.info(
"case: %s, suite: %s, area: %s, "
+ "category: %s, tags: %s, priority: %s",
metadata.name,
metadata.suite.name,
metadata.suite.area,
metadata.suite.category,
",".join(metadata.suite.tags),
metadata.priority,
f"case: {metadata.name}, suite: {metadata.suite.name}, "
f"area: {metadata.suite.area}, "
f"category: {metadata.suite.category}, "
f"tags: {','.join(metadata.suite.tags)}, "
f"priority: {metadata.priority}"
)
else:
log.error("TODO: cannot list selected cases yet.")
else:
raise Exception("unknown list type '%s'" % args.type)
raise Exception(f"unknown list type '{args.type}'")
log.info("list information here")

Просмотреть файл

@ -47,8 +47,7 @@ class Action(metaclass=ABCMeta):
def setStatus(self, status: ActionStatus) -> None:
if self.__status != status:
log.info(
"%s status changed from %s to %s"
% (self.name, self.__status.name, status.name)
f"{self.name} status changed from {self.__status.name} to {status.name}"
)
self.__status = status

Просмотреть файл

@ -5,19 +5,20 @@ from lisa.core.testFactory import TestFactory
from lisa.util.logger import log
class CaseMetadata(object):
def __init__(self, priority: Optional[int]) -> None:
class CaseMetadata:
def __init__(self, description: str, priority: Optional[int]) -> None:
self.priority = priority
self.description = description
def __call__(self, func: Callable[..., None]) -> Callable[..., None]:
factory = TestFactory()
factory.addTestMethod(func, self.priority)
factory.addTestMethod(func, self.description, self.priority)
def wrapper(*args: object) -> None:
log.info("case '%s' started", func.__name__)
log.info(f"case '{func.__name__}' started")
start = timer()
func(*args)
end = timer()
log.info("case '%s' ended with %f", func.__name__, end - start)
log.info(f"case '{func.__name__}' ended with {end - start}")
return wrapper

Просмотреть файл

@ -11,16 +11,24 @@ if TYPE_CHECKING:
class SuiteMetadata:
def __init__(
self, area: str, category: str, tags: List[str], name: Optional[str] = None
self,
area: str,
category: str,
description: str,
tags: List[str],
name: Optional[str] = None,
) -> None:
self.area = area
self.category = category
self.tags = tags
self.description = description
self.name = name
def __call__(self, test_class: Type[TestSuite]) -> Callable[..., object]:
factory = TestFactory()
factory.addTestClass(test_class, self.area, self.category, self.tags, self.name)
factory.addTestClass(
test_class, self.area, self.category, self.description, self.tags, self.name
)
def wrapper(
test_class: Type[TestSuite], environment: Environment, cases: List[str]

Просмотреть файл

@ -77,7 +77,7 @@ class Environment(object):
spec[constants.ENVIRONMENTS_NODES] = nodes_spec
environment.spec = spec
log.debug("environment spec is %s", environment.spec)
log.debug(f"environment spec is {environment.spec}")
return environment
@property
@ -102,7 +102,7 @@ class Environment(object):
found = node
break
if throwError:
raise Exception("cannot find node %s" % (name))
raise Exception(f"cannot find node {name}")
else:
if throwError:
raise Exception("nodes shouldn't be None when call getNodeByName")

Просмотреть файл

@ -43,6 +43,6 @@ class EnvironmentFactory:
key = name.lower()
environmet = self.environments.get(key)
if environmet is None:
raise Exception("not found environment '%s'", name)
raise Exception(f"not found environment '{name}'")
return environmet

Просмотреть файл

@ -3,7 +3,7 @@ from abc import ABC
from lisa import Node
class ExecutableBase(ABC):
class Executable(ABC):
def __init__(self) -> None:
self.node = None

Просмотреть файл

@ -35,7 +35,7 @@ class Node:
elif node_type == constants.ENVIRONMENTS_NODES_LOCAL:
isRemote = False
else:
raise Exception("unsupported node_type '%s'", node_type)
raise Exception(f"unsupported node_type '{node_type}'")
node = Node(spec=spec, isRemote=isRemote, isDefault=isDefault)
return node
@ -74,7 +74,7 @@ class Node:
result: ExecutableResult
cmd_id = random.randint(0, 10000)
start_timer = timer()
log.debug("remote(%s) cmd[%s] %s", self.isRemote, cmd_id, cmd)
log.debug(f"remote({self.isRemote}) cmd[{cmd_id}] {cmd}")
if self.isRemote:
# remote
if self.connection is None:
@ -87,7 +87,7 @@ class Node:
process.start(cmd)
result = process.waitResult()
end_timer = timer()
log.info("cmd[%s] executed with %f", cmd_id, end_timer - start_timer)
log.info(f"cmd[{cmd_id}] executed with {end_timer - start_timer}")
return result
def cleanup(self) -> None:

Просмотреть файл

@ -30,7 +30,7 @@ class NodeFactory:
config.get(constants.ENVIRONMENTS_NODES_REMOTE_PRIVATEKEYFILE),
)
if node is not None:
log.debug("created node '%s'", node_type)
log.debug(f"created node '{node_type}'")
return node
@staticmethod

Просмотреть файл

@ -18,7 +18,7 @@ def import_module(path: str, logDetails: bool = True) -> None:
package_dir = os.path.dirname(path)
sys.path.append(package_dir)
if logDetails:
log.info("loading extension from %s", path)
log.info(f"loading extension from {path}")
for file in glob(os.path.join(path, "**", "*.py"), recursive=True):
file_name = os.path.basename(file)
@ -26,8 +26,8 @@ def import_module(path: str, logDetails: bool = True) -> None:
package_dir_len = len(package_dir) + 1
local_package_name = dir_name[package_dir_len:]
local_package_name = local_package_name.replace("\\", ".").replace("/", ".")
local_module_name = ".%s" % os.path.splitext(file_name)[0]
full_module_name = "%s%s" % (local_package_name, local_module_name)
local_module_name = f".{os.path.splitext(file_name)[0]}"
full_module_name = f"{local_package_name}{local_module_name}"
if file_name.startswith("__"):
continue
@ -35,12 +35,10 @@ def import_module(path: str, logDetails: bool = True) -> None:
if full_module_name not in sys.modules:
if logDetails:
log.debug(
"loading file %s, package %s, full_module_name %s, "
"local_module_name %s",
file,
local_package_name,
full_module_name,
local_module_name,
f"loading file {file}, "
f"package {local_package_name}, "
f"full_module_name {full_module_name}, "
f"local_module_name {local_module_name}"
)
importlib.import_module(local_module_name, package=local_package_name)

Просмотреть файл

@ -18,7 +18,9 @@ class PlatformFactory:
if self.platforms.get(platform_type) is None:
self.platforms[platform_type] = platform()
else:
raise Exception("platform '%s' exists, cannot be registered again")
raise Exception(
f"platform '{platform_type}' exists, cannot be registered again"
)
def initializePlatform(self, config: Optional[List[Dict[str, object]]]) -> Platform:
@ -34,14 +36,14 @@ class PlatformFactory:
self._buildFactory()
log.debug(
"registered platforms [%s]",
", ".join([name for name in self.platforms.keys()]),
f"registered platforms: "
f"[{', '.join([name for name in self.platforms.keys()])}]"
)
platform = self.platforms.get(platform_type.lower())
if platform is None:
raise Exception("cannot find platform type '%s'" % platform_type)
log.info("activated platform '%s'", platform_type)
raise Exception(f"cannot find platform type '{platform_type}'")
log.info(f"activated platform '{platform_type}'")
platform.config(constants.CONFIG_CONFIG, config[0])
return platform

Просмотреть файл

@ -32,10 +32,8 @@ class RuntimeObject:
warn_as_error, "the ready platform cannot process environment spec"
)
def _validateMessage(
self, warn_as_error: Optional[bool], message: str, *args: str
) -> None:
def _validateMessage(self, warn_as_error: Optional[bool], message: str) -> None:
if warn_as_error:
raise Exception(message % args)
raise Exception(message)
else:
log.warn(message, *args)
log.warn(message)

Просмотреть файл

@ -8,7 +8,11 @@ from lisa.util.logger import log
class TestCaseMetadata:
def __init__(
self, method: Callable[[], None], priority: Optional[int] = 2, name: str = "",
self,
method: Callable[[], None],
description: str,
priority: Optional[int] = 2,
name: str = "",
):
if name is not None and name != "":
self.name = name
@ -17,6 +21,7 @@ class TestCaseMetadata:
self.key: str = self.name.lower()
self.full_name = method.__qualname__.lower()
self.method = method
self.description = description
self.priority = priority
self.suite: TestSuiteMetadata
@ -27,6 +32,7 @@ class TestSuiteMetadata:
test_class: Type[TestSuite],
area: Optional[str],
category: Optional[str],
description: str,
tags: List[str],
name: str = "",
):
@ -38,6 +44,7 @@ class TestSuiteMetadata:
self.key = self.name.lower()
self.area = area
self.category = category
self.description = description
self.tags = tags
self.cases: Dict[str, TestCaseMetadata] = dict()
@ -46,7 +53,7 @@ class TestSuiteMetadata:
self.cases[test_case.key] = test_case
else:
raise Exception(
"TestSuiteMetadata has test method %s already" % test_case.key
f"TestSuiteMetadata has test method {test_case.key} already"
)
@ -61,6 +68,7 @@ class TestFactory:
test_class: Type[TestSuite],
area: Optional[str],
category: Optional[str],
description: str,
tags: List[str],
name: Optional[str],
) -> None:
@ -71,31 +79,32 @@ class TestFactory:
key = name.lower()
test_suite = self.suites.get(key)
if test_suite is None:
test_suite = TestSuiteMetadata(test_class, area, category, tags)
test_suite = TestSuiteMetadata(
test_class, area, category, description, tags
)
self.suites[key] = test_suite
else:
raise Exception("TestFactory duplicate test class name: %s" % key)
raise Exception(f"TestFactory duplicate test class name: {key}")
class_prefix = "%s." % key
class_prefix = f"{key}."
for test_case in self.cases.values():
if test_case.full_name.startswith(class_prefix):
self._addCaseToSuite(test_suite, test_case)
log.info(
"registered test suite '%s' with test cases: '%s'",
test_suite.key,
", ".join([key for key in test_suite.cases]),
f"registered test suite '{test_suite.key}' "
f"with test cases: '{', '.join([key for key in test_suite.cases])}'"
)
def addTestMethod(
self, test_method: Callable[[], None], priority: Optional[int]
self, test_method: Callable[[], None], description: str, priority: Optional[int]
) -> None:
test_case = TestCaseMetadata(test_method, priority)
test_case = TestCaseMetadata(test_method, description, priority)
full_name = test_case.full_name
if self.cases.get(full_name) is None:
self.cases[full_name] = test_case
else:
raise Exception("duplicate test class name: %s" % full_name)
raise Exception(f"duplicate test class name: {full_name}")
# this should be None in current observation.
# the methods are loadded prior to test class
@ -104,7 +113,7 @@ class TestFactory:
class_name = full_name.split(".")[0]
test_suite = self.suites.get(class_name)
if test_suite is not None:
log.debug("add case '%s' to suite '%s'", test_case.name, test_suite.name)
log.debug(f"add case '{test_case.name}' to suite '{test_suite.name}'")
self._addCaseToSuite(test_suite, test_case)
def _addCaseToSuite(

Просмотреть файл

@ -11,19 +11,15 @@ if TYPE_CHECKING:
class TestSuite(Action, metaclass=ABCMeta):
area: str = ""
category: str = ""
tags: List[str] = []
def __init__(self, environment: Environment, cases: List[str]) -> None:
self.environment = environment
self.cases = cases
self.shouldStop = False
def suiteSetup(self) -> None:
def beforeSuite(self) -> None:
pass
def suiteCleanup(self) -> None:
def afterSuite(self) -> None:
pass
def beforeCase(self) -> None:
@ -36,7 +32,7 @@ class TestSuite(Action, metaclass=ABCMeta):
return "TestSuite"
async def start(self) -> None:
self.suiteSetup()
self.beforeSuite()
for test_case in self.cases:
self.beforeCase()
test_method = getattr(self, test_case)
@ -46,7 +42,7 @@ class TestSuite(Action, metaclass=ABCMeta):
log.info("received stop message, stop run")
self.setStatus(ActionStatus.STOPPED)
break
self.suiteCleanup()
self.afterSuite()
async def stop(self) -> None:
self.setStatus(ActionStatus.STOPPING)

Просмотреть файл

@ -12,12 +12,11 @@ from lisa.util.logger import init_log, log
@retry(FileExistsError, tries=10, delay=0) # type: ignore
def create_result_path() -> str:
path_template = "runtime/results/{0}/{0}-{1}"
date = datetime.utcnow().strftime("%Y%m%d")
time = datetime.utcnow().strftime("%H%M%S-%f")[:-3]
current_path = path_template.format(date, time)
current_path = f"runtime/results/{date}/{date}-{time}"
if os.path.exists(current_path):
raise FileExistsError("%s exists, and not found an unique path." % current_path)
raise FileExistsError(f"{current_path} exists, and not found an unique path.")
return current_path
@ -30,17 +29,16 @@ def main() -> None:
args = parse_args()
init_log()
log.info("Python version: %s" % sys.version)
log.info("local time: %s", datetime.now().astimezone())
log.info("command line args: %s" % sys.argv)
log.info("result path: %s", env.get_env(env.RESULT_PATH))
log.info(f"Python version: {sys.version}")
log.info(f"local time: {datetime.now().astimezone()}")
log.info(f"command line args: {sys.argv}")
log.info(f"result path: {env.get_env(env.RESULT_PATH)}")
if args.debug:
log_level = DEBUG
else:
log_level = INFO
log.setLevel(log_level)
log.info("show debug log: %s", args.debug)
args.func(args)

Просмотреть файл

@ -10,15 +10,15 @@ from lisa.util.logger import log
def parse(args: Namespace) -> Config:
path = args.config
path = os.path.realpath(path)
log.info("load config from: %s", path)
log.info(f"load config from: {path}")
if not os.path.exists(path):
raise FileNotFoundError(path)
with open(path, "r") as file:
data = yaml.safe_load(file)
log.debug("final config data: %s", data)
log.debug(f"final config data: {data}")
base_path = os.path.dirname(path)
log.debug("base path is %s", base_path)
log.debug(f"base path is {base_path}")
config = Config(base_path, data)
return config

Просмотреть файл

@ -29,10 +29,11 @@ class LISARunner(TestRunner):
suites = test_factory.suites
environment_factory = EnvironmentFactory()
platform_type = self.platform.platformType()
# request environment
log.info("platform %s environment requesting", self.platform.platformType())
log.info(f"platform {platform_type} environment requesting")
environment = environment_factory.getEnvironment()
log.info("platform %s environment requested", self.platform.platformType())
log.info(f"platform {platform_type} environment requested")
for suite in suites.values():
test_object: TestSuite = suite.test_class(
@ -41,9 +42,9 @@ class LISARunner(TestRunner):
await test_object.start()
# delete enviroment after run
log.info("platform %s environment deleting", self.platform.platformType())
log.info(f"platform {platform_type} environment {environment.name} deleting")
self.platform.deleteEnvironment(environment)
log.info("platform %s environment deleted", self.platform.platformType())
log.info(f"platform {platform_type} environment {environment.name} deleted")
self.setStatus(ActionStatus.SUCCESS)

Просмотреть файл

@ -6,10 +6,10 @@ __prefix = "LISA_"
def set_env(name: str, value: str, isSecret: bool = False) -> None:
name = "%s%s" % (__prefix, name)
name = f"{__prefix}{name}"
os.environ[name] = value
def get_env(name: str) -> str:
name = "%s%s" % (__prefix, name)
name = f"{__prefix}{name}"
return os.environ[name]

Просмотреть файл

@ -13,7 +13,7 @@ def init_log() -> None:
format=format,
datefmt="%m%d %H:%M:%S",
handlers=[
logging.FileHandler("%s/lisa-host.log" % os.getenv(env_result_path)),
logging.FileHandler(f"{os.getenv(env_result_path)}/lisa-host.log"),
logging.StreamHandler(),
],
)

Просмотреть файл

@ -103,7 +103,7 @@ class Process:
)
self._running = True
if self.process is not None:
log.debug("process %s started", self.process.pid)
log.debug(f"process {self.process.pid} started")
def waitResult(self, timeout: float = 600) -> ExecutableResult:
budget_time = timeout
@ -116,7 +116,7 @@ class Process:
if budget_time < 0:
if self.process is not None:
log.warn("process %s timeout in %s sec", self.process.pid, timeout)
log.warn(f"process {self.process.pid} timeout in {timeout} sec")
self.stop()
# cleanup to get pipe complete
@ -139,7 +139,7 @@ class Process:
for child in children:
child.terminate()
self.process.terminate()
log.debug("process %s stopped", self.process.pid)
log.debug(f"process {self.process.pid} stopped")
def cleanup(self) -> None:
if self.stdout_pipe is not None:
@ -151,7 +151,7 @@ class Process:
self.exitCode = self.getExitCode()
if self.exitCode is not None and self.process is not None:
if self._running is True:
log.debug("process %s exited: %s", self.process.pid, self.exitCode)
log.debug(f"process {self.process.pid} exited: {self.exitCode}")
self._running = False
return self._running