blob: a505bb05640aada5a7da9352b79b3c50031939ae [file] [log] [blame]
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
load("@proto//chromiumos/config/api/test/harness/tnull/v1/tnull.proto",
tnull_pb = "chromiumos.config.api.test.tnull.v1"
)
# Names from this lib: ArchiveArtifactRequest, ArchiveArtifactResponse,
# ProgressSinkClientConfig, ReportLogRequest, ReportLogResponse,
# ReportResultRequest, ReportResultResponse, Result
load("@proto//chromiumos/config/api/test/rtd/v1/progress.proto",
progress_pb = "chromiumos.config.api.test.rtd.v1"
)
load("@proto//chromiumos/config/api/test/metadata/v1/metadata.proto",
metadata_pb = "chromiumos.config.api.test.metadata.v1"
)
load("@proto//chromiumos/config/api/test/results/v2/result.proto",
result_pb = "chromiumos.config.api.test.results.v2"
)
load("@proto//google/protobuf/struct.proto", google_pb = "google.protobuf")
_TEST_NAME_PREFIX = "remoteTestDrivers/tnull/tests/"
def _define_test(
test_name,
purpose,
doc,
attrs = None,
owner_emails = None,
owner_groups = None,
setup = None,
steps = None,
):
if not setup:
setup = _define_setup()
contacts = (
[metadata_pb.Contact(email = e) for e in (owner_emails or [])]
+ [metadata_pb.Contact(mdb_group = g) for g in (owner_groups or [])]
)
step_proto = tnull_pb.Steps(
setup = setup,
steps = [_define_step(step) for step in (steps or [])],
)
step_struct = proto.from_jsonpb(
google_pb.Struct,
proto.to_jsonpb(step_proto)
)
details = google_pb.Struct(fields = {
"purpose": google_pb.Value(string_value = purpose),
"doc": google_pb.Value(string_value = doc),
"steps": google_pb.Value(struct_value=step_struct),
})
info = metadata_pb.Informational(
authors = contacts,
details = details,
)
return metadata_pb.Test(
name = _TEST_NAME_PREFIX + test_name,
attributes = [metadata_pb.Attribute(name = a) for a in (attrs or [])],
informational = info,
)
def _define_step(step):
"""
Args:
step, dict with the format
{
"method": <string naming the step type>,
"args": {
"common": { <dict of CommonArgs> },
<other arguments to pass to the step proto's constructor>
},
}
The method field is required; args is optional, and common is optional even
if args is present.
Returns:
a tnull_pb.Step object
"""
args = step.get("args", {})
step_constructor = {
# case step["method"]:
"archive": tnull_pb.ArchiveStep,
"log": tnull_pb.LogStep,
"result": tnull_pb.ResultStep,
}.get(step.get("method"),
# default:
None
)
if step_constructor == None:
return tnull_pb.Step(other=tnull_pb.UnknownStep(args=args))
common = tnull_pb.CommonArgSet(**args.pop("common_args", {}))
substep = step_constructor(common_args=common, **args)
return tnull_pb.Step(**{step["method"]: substep})
simple_success_result = result_pb.Result(state=result_pb.Result.SUCCEEDED)
def _define_setup(sink_config = None, setup_config = None):
"""
Args:
sink_config: progress_pb.ProgressSinkClientConfig object
setup_config: dict with arguments for results/logs/artifacts to include
Returns:
tnull_pb.SetupStep object
"""
if not sink_config:
sink_config = progress_pb.ProgressSinkClientConfig()
if not setup_config:
setup_config = {}
return tnull_pb.SetupStep(
config = sink_config,
result = simple_success_result,
)
def _make_result(result):
"""
Args:
result, dict with the format:
{
"skipped": boolean, default false; if true, treat error-free result as
SKIPPED rather than as SUCCEEDED.
"errors": list of structs, each specifying an error with source
and severity.
}
Returns:
progress_pb.Result object
"""
if result == None:
return simple_success_result
errs = _format_errors(result.get("errors", []))
if result.get("skipped", False):
state = result_pb.Result.SKIPPED
elif len(errs) == 0:
state = result_pb.Result.SUCCEEDED
elif any([err.severity == result_pb.Result.Error.CRITICAL for err in errs]):
state = result_pb.Result.FAILED
else:
state = result_pb.Result.SUCCEEDED
return result_pb.Result(state=state, errors=errs)
def _format_errors(err_structs):
"""
Args:
err_structs: list of struct(source= <str>, severity= <str>)
Returns:
list of progress_pb.Result.Error objects
"""
fixed_up_structs = [_fixup_error(s) for s in err_structs]
return [_make_error(s.source, s.severity) for s in fixed_up_structs]
def _fixup_error(err_struct):
if not getattr(err_struct, "source", None):
fail("Invalid args for error: %s" % err_struct.to_json)
sev = getattr(err_struct, "severity", None)
return struct(source = err_struct.source, severity = sev)
def _make_error(source, severity):
"""
Args:
source: one of ["test", "rtd", "tls"]
severity: one of ["error", "critical", "warn"]
Returns:
progress_pb.Error object
"""
src = _get_source(source)
if src == None:
return fail("Error source unspecified")
return result_pb.Result.Error(
source = src,
severity = _get_severity(severity)
)
# map string or int to the appropriate constant. Default to None.
def _get_source(key):
sources = {
"rtd": result_pb.Result.Error.RTD,
"test": result_pb.Result.Error.TEST,
"tls": result_pb.Result.Error.TEST_LAB_SERVICES,
}
return sources.get(key)
# map string or int to the appropriate constant. Default to critical.
def _get_severity(key):
severities = {
"critical": result_pb.Result.Error.CRITICAL,
"error": result_pb.Result.Error.CRITICAL,
"warn": result_pb.Result.Error.WARNING,
"warning": result_pb.Result.Error.WARNING,
}
return severities.get(key, result_pb.Result.Error.WARNING)
test_common = struct(
define_test = _define_test,
define_setup = _define_setup,
)