[PATCH 0/9] command results refactoring
by Ondrej Lichtner
From: Ondrej Lichtner <olichtne(a)redhat.com>
The following patch series refactors how lnst works with command results.
The most visible change is the change in results showing up in logs, where we
now show formatted result data along with the simple PASS/FAIL result of the
command. The same formatted result data is also shown in the result summary at
the end of a lnst-ctl run.
>From an advanced users point of view the interface of setting results in your
custom test modules changed a bit. You are now required to use methods
set_pass() or set_fail() to correctly set a result. By limiting the interface
to these two functions we can easily ensure that the result object will
correctly constructed to suit the rest of lnst.
Both of these methods take a single argument res_data that can contain any
additional data you want to append to the results. These might be messages to
be printed or measured values or anything really. The only restriction here is
that it must contain only nested dictionaries, lists, and stringable objects.
The reason behind this is that we serialize this into an xml file (-x argument)
which also restrict you to use strings that will be valid in the xml file.
Formatting of the reported result data was also moved from the controller to
the slave, directly into the corresponding command object. This simplifies code
and also enables users to implement their own format_res_data function in their
custom test module classes that will be used to format the result data passed
to the set_{pass, fail} functions. The formatting function is called
automatically so you only need to override it.
Finally the NetTestResultSerializer class was reimplemented to better suit the
current model of result handling. The results are no longer stored as a DOM
tree and then transformed into the summary at end. Instead the data is stored
in a better custom data structure that can be easily iterated over to generate
both the summary and xml output.
The rest of the changes are small adjustments to numerous parts of lnst code so
that everything works.
Ondrej Lichtner (9):
NetTestResultSerializer: rename cmd_seq to task
NetTestCommand: command result interface change
NetTestCommand: fix control commands
Machine: remove expect checking
NetTestController: accept new result dictionaries
NetTestResultSerializer: new results reimplementation
smoke tests: move expect attribute of bg cmd
test_modules: update result reporting
NetTestCommand: add method check_res_data
lnst-ctl | 2 +-
lnst/Common/NetTestCommand.py | 151 +++++++++++++---
lnst/Common/TestsCommon.py | 26 ++-
lnst/Controller/Machine.py | 7 -
lnst/Controller/NetTestController.py | 27 ++-
lnst/Controller/NetTestResultSerializer.py | 265 ++++++++++-------------------
lnst/Slave/NetTestSlave.py | 6 +-
recipes/smoke/lib/task-bg.xml | 4 +-
test_modules/TestDummyFailing.py | 3 +-
test_modules/TestIcmp6Ping.py | 14 +-
test_modules/TestIcmpPing.py | 14 +-
test_modules/TestIperf.py | 5 +-
test_modules/TestMulticast.py | 3 +-
test_modules/TestNetCat.py | 6 +-
test_modules/TestPacketAssert.py | 3 +-
test_modules/TestPktgenTx.py | 3 +-
16 files changed, 290 insertions(+), 249 deletions(-)
--
1.8.3.1
10 years, 6 months
[lnst] smoke tests: move expect attribute of bg cmd
by Jiří Pírko
commit 91b754132ff11bf252c74179b3b7272aa1f5048c
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 4 13:36:06 2013 +0200
smoke tests: move expect attribute of bg cmd
The expect attribute for bg commands should now be specified in the bg
command tag instead of the associated control command.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
recipes/smoke/lib/task-bg.xml | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
---
diff --git a/recipes/smoke/lib/task-bg.xml b/recipes/smoke/lib/task-bg.xml
index b9388d2..27536d8 100644
--- a/recipes/smoke/lib/task-bg.xml
+++ b/recipes/smoke/lib/task-bg.xml
@@ -1,5 +1,5 @@
<task>
- <run bg_id="1" machine="1" module="IcmpPing" timeout="30">
+ <run bg_id="1" expect="#icmp_result#" machine="1" module="IcmpPing" timeout="30">
<options>
<option name="addr" value="{ip(2,testiface)}"/>
<option name="count" value="40"/>
@@ -8,5 +8,5 @@
</options>
</run>
<ctl_wait seconds="5"/>
- <intr bg_id="1" expect="#icmp_result#" machine="1" timeout="30"/>
+ <intr bg_id="1" machine="1" timeout="30"/>
</task>
10 years, 6 months
[lnst] NetTestResultSerializer: new results reimplementation
by Jiří Pírko
commit 1a943fc7c4342e1cce59eef49a1e2d43c705db06
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 4 13:36:05 2013 +0200
NetTestResultSerializer: new results reimplementation
This commit reimplements the result serializer so that it doesn't
automatically create a DOM tree out of the supplied results. Instead
results are stored in a nice dictionary hierarchy.
This changed combined with moving the res_data formatting done on slaves
makes it very easy to print the summary. And at the same time be able to
easily create an xml file.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst-ctl | 2 +-
lnst/Controller/NetTestController.py | 2 +
lnst/Controller/NetTestResultSerializer.py | 265 +++++++++-------------------
3 files changed, 91 insertions(+), 178 deletions(-)
---
diff --git a/lnst-ctl b/lnst-ctl
index 55e2dd1..43770b2 100755
--- a/lnst-ctl
+++ b/lnst-ctl
@@ -183,7 +183,7 @@ def main():
if result_path:
result_path = os.path.expanduser(result_path)
handle = open(result_path, "w")
- handle.write(str(res_serializer))
+ handle.write(res_serializer.get_result_xml())
handle.close()
if __name__ == "__main__":
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py
index c7d1fab..8045aa7 100644
--- a/lnst/Controller/NetTestController.py
+++ b/lnst/Controller/NetTestController.py
@@ -495,6 +495,8 @@ class NetTestController:
command["seconds"]),
"msg": "",
"res_data": None}
+ if self._res_serializer:
+ self._res_serializer.add_cmd_result(command, cmd_res)
return cmd_res
machine_id = command["machine"]
diff --git a/lnst/Controller/NetTestResultSerializer.py b/lnst/Controller/NetTestResultSerializer.py
index d245568..514988b 100644
--- a/lnst/Controller/NetTestResultSerializer.py
+++ b/lnst/Controller/NetTestResultSerializer.py
@@ -44,213 +44,71 @@ def get_node_val(node):
class NetTestResultSerializer:
def __init__(self):
- impl = getDOMImplementation()
- self._dom = impl.createDocument(None, "results", None)
- self._top_el = self._dom.documentElement
- self._cur_recipe_el = None
- self._cur_cmd_seq_el = None
-
- def __str__(self):
- return self._dom.toprettyxml()
+ self._results = []
def add_recipe(self, name):
- recipe_el = self._dom.createElement("recipe")
- recipe_el.setAttribute("name", name)
- recipe_el.setAttribute("result", "FAIL")
- self._top_el.appendChild(recipe_el)
- self._cur_recipe_el = recipe_el
- self._cur_cmd_seq_el = None
+ recipe_result = {"name": name,
+ "result": "FAIL",
+ "tasks": []}
+ self._results.append(recipe_result)
def set_recipe_result(self, result):
if result and result["passed"]:
- self._cur_recipe_el.setAttribute("result", "PASS")
+ self._results[-1]["result"] = "PASS"
else:
- self._cur_recipe_el.setAttribute("result", "FAIL")
+ self._results[-1]["result"] = "FAIL"
+
if "err_msg" in result:
- err_el = self._dom.createElement("error_message")
- err_text = self._dom.createTextNode(result["err_msg"])
- err_el.appendChild(err_text)
- self._cur_recipe_el.appendChild(err_el)
+ self._results[-1]["err_msg"] = result["err_msg"]
def add_task(self):
- cmd_seq_el = self._dom.createElement("command_sequence")
- self._cur_recipe_el.appendChild(cmd_seq_el)
- self._cur_cmd_seq_el = cmd_seq_el
+ self._results[-1]["tasks"].append([])
def add_cmd_result(self, command, cmd_res):
- command_el = self._dom.createElement("command")
- self._cur_cmd_seq_el.appendChild(command_el)
-
- for key in command:
- if key == "options":
- continue
- command_el.setAttribute(key, str(command[key]))
-
- result_el = self._dom.createElement("result")
- command_el.appendChild(result_el)
-
- if cmd_res["passed"]:
- res = "PASS"
- else:
- res = "FAIL"
- result_el.setAttribute("result", res)
-
- if "err_msg" in cmd_res:
- err_el = self._dom.createElement("error_message")
- err_text = self._dom.createTextNode(cmd_res["err_msg"])
- err_el.appendChild(err_text)
- result_el.appendChild(err_el)
-
- if "res_data" in cmd_res:
- res_data_el = self._dom.createElement("result_data")
- serialize_obj(cmd_res["res_data"], self._dom, res_data_el)
- command_el.appendChild(res_data_el)
+ self._results[-1]["tasks"][-1].append((command, cmd_res))
def print_summary(self):
output_pairs = []
- for recipe in self._top_el.getElementsByTagName("recipe"):
- recipe_name = recipe.getAttribute("name")
- recipe_res = recipe.getAttribute("result")
- output_pairs.append((recipe_name, recipe_res))
-
- if recipe_res == "FAIL":
- err_node = None
- for child in recipe.childNodes:
- if child.nodeName == "error_message":
- err_node = child
- break
- if err_node:
- text = get_node_val(err_node)
- output_pairs.append((4*" "+"error message: "+text, ""))
+ for recipe in self._results:
+ output_pairs.append((recipe["name"], recipe["result"]))
+
+ if recipe["result"] == "FAIL" and \
+ "err_msg" in recipe and recipe["err_msg"] != "":
+ err_msg = recipe["err_msg"]
+ output_pairs.append((4*" " + "error message:" + err_msg, ""))
seq_num = 1
- for cmd_seq in recipe.getElementsByTagName("command_sequence"):
- command_sequence = 4*" "+"task: %s" % seq_num
- output_pairs.append((command_sequence, ""))
+ for task in recipe["tasks"]:
+ output_pairs.append((4*" " + "task: %s" % seq_num,""))
- seq_num = seq_num + 1
+ seq_num += 1
- for command in cmd_seq.getElementsByTagName("command"):
- self._format_command(command, output_pairs)
+ for cmd, cmd_res in task:
+ self._format_command(cmd, cmd_res, output_pairs)
self._print_pairs(output_pairs)
- def _format_command(self, command, output_pairs):
- cmd_type = command.getAttribute("type")
- if cmd_type == "test":
- self._format_test_command(command, output_pairs)
- elif cmd_type == "wait":
- self._format_wait_command(command, output_pairs)
- elif cmd_type == "intr":
- self._format_intr_command(command, output_pairs)
- elif cmd_type == "kill":
- self._format_kill_command(command, output_pairs)
- elif cmd_type == "ctl_wait":
- self._format_ctl_wait_command(command, output_pairs)
- elif cmd_type == "exec":
- self._format_exec_command(command, output_pairs)
- elif cmd_type == "config":
- self._format_config(command, output_pairs)
-
- result_node = command.getElementsByTagName("result")[0]
- cmd_res = result_node.getAttribute("result")
-
- if cmd_res == "FAIL":
- err_node = result_node.getElementsByTagName("error_message")
- if len(err_node) != 0:
- err_node = err_node[0]
- text = get_node_val(err_node)
- output_pairs.append((12*" "+"error message: "+text, ""))
-
- def _format_test_command(self, command, output_pairs):
- result_node = command.getElementsByTagName("result")[0]
- cmd_res = result_node.getAttribute("result")
-
- cmd_val = command.getAttribute("module")
- cmd_type = command.getAttribute("type")
- if command.hasAttribute("bg_id"):
- bg_id = " bg_id: %s" % command.getAttribute("bg_id")
- else:
- bg_id = ""
- cmd = 8*" "+"%-14s%s%s" %(cmd_type, cmd_val, bg_id)
- output_pairs.append((cmd, cmd_res))
-
- def _format_wait_command(self, command, output_pairs):
- result_node = command.getElementsByTagName("result")[0]
- cmd_res = result_node.getAttribute("result")
-
- cmd_val = command.getAttribute("proc_id")
- cmd_type = command.getAttribute("type")
- if command.hasAttribute("bg_id"):
- bg_id = " bg_id: %s" % command.getAttribute("bg_id")
+ def _format_command(self, command, cmd_res, output_pairs):
+ if cmd_res["passed"]:
+ res = "PASS"
else:
- bg_id = ""
- cmd = 8*" "+"%-13s id: %s%s" %(cmd_type, cmd_val, bg_id)
- output_pairs.append((cmd, cmd_res))
-
- def _format_intr_command(self, command, output_pairs):
- self._format_wait_command(command, output_pairs)
-
- def _format_kill_command(self, command, output_pairs):
- self._format_wait_command(command, output_pairs)
+ res = "FAIL"
+ output_pairs.append((8*" " + cmd_res["res_header"], res))
- def _format_exec_command(self, command, output_pairs):
- result_node = command.getElementsByTagName("result")[0]
- cmd_res = result_node.getAttribute("result")
+ if "msg" in cmd_res and cmd_res["msg"] != "":
+ output_pairs.append((12*" " + "message: " + cmd_res["msg"], ""))
- cmd_val = command.getAttribute("command")
- cmd_type = command.getAttribute("type")
- if command.hasAttribute("bg_id"):
- bg_id = " bg_id: %s" % command.getAttribute("bg_id")
- else:
- bg_id = ""
- cmd = 8*" "+"%-14s%s%s" %(cmd_type, cmd_val, bg_id)
- output_pairs.append((cmd, cmd_res))
-
- def _format_ctl_wait_command(self, command, output_pairs):
- result_node = command.getElementsByTagName("result")[0]
- cmd_res = result_node.getAttribute("result")
-
- cmd_val = command.getAttribute("seconds")
- cmd_type = command.getAttribute("type")
- cmd = 8*" "+"%-14s%ss" %(cmd_type, cmd_val)
- output_pairs.append((cmd, cmd_res))
-
- def _format_config(self, command, output_pairs):
- result_node = command.getElementsByTagName("result")[0]
- cmd_res = result_node.getAttribute("result")
-
- cmd_type = command.getAttribute("type")
- if command.hasAttribute("bg_id"):
- bg_id = " bg_id: %s" % command.getAttribute("bg_id")
- else:
- bg_id = ""
- cmd = 8*" "+"%-14s%s" %(cmd_type, bg_id)
- output_pairs.append((cmd, cmd_res))
-
- result_data_nodes = command.getElementsByTagName("result_data")
- if len(result_data_nodes) != 0:
- result_data_node = result_data_nodes[0]
- options_nodes = result_data_node.getElementsByTagName("options")
- for options_node in options_nodes:
- for option in options_node.getElementsByTagName("options_item"):
- previous_node = option.getElementsByTagName("previous_val")[0]
- current_node = option.getElementsByTagName("current_val")[0]
- name_node = option.getElementsByTagName("name")[0]
- previous_val = get_node_val(previous_node)
- current_val = get_node_val(current_node)
- name = get_node_val(name_node)
- opt_left = 12*" "+"%s" % name
- opt_right = "previous: %s current: %s" \
- % (previous_val, current_val)
- output_pairs.append((opt_left, opt_right))
+ if "report" in cmd_res and cmd_res["report"] != "":
+ for line in cmd_res["report"].splitlines():
+ out = decorate_with_preset(line, "faded")
+ output_pairs.append((12*" " + out, ""))
def _print_pairs(self, output_pairs):
max_left = 0
max_right = 0
for left, right in output_pairs:
- if len(left) > max_left and right != "":
+ if len(left) > max_left:
max_left = len(left)
if len(right) > max_right:
max_right = len(right)
@@ -282,3 +140,56 @@ class NetTestResultSerializer:
output = left + " "
logging.info(" %s " % output)
logging.info("="*(full_length))
+
+ def get_result_xml(self):
+ impl = getDOMImplementation()
+ doc = impl.createDocument(None, "results", None)
+ top_el = doc.documentElement
+
+ for recipe in self._results:
+ recipe_el = doc.createElement("recipe")
+ recipe_el.setAttribute("name", recipe["name"])
+ recipe_el.setAttribute("result", recipe["result"])
+
+ top_el.appendChild(recipe_el)
+
+ if recipe["result"] == "FAIL" and \
+ "err_msg" in recipe and recipe["err_msg"] != "":
+ err_el = doc.createElement("error_message")
+ err_text = doc.createTextNode(recipe["err_msg"])
+ err_el.appendChild(err_text)
+ recipe_el.appendChild(err_el)
+
+ for task in recipe["tasks"]:
+ cmd_seq_el = doc.createElement("command_sequence")
+ recipe_el.appendChild(cmd_seq_el)
+
+ for cmd, cmd_res in task:
+ command_el = doc.createElement("command")
+ cmd_seq_el.appendChild(command_el)
+
+ for key in cmd:
+ if key == "options":
+ continue
+ command_el.setAttribute(key, str(cmd[key]))
+
+ result_el = doc.createElement("result")
+ command_el.appendChild(result_el)
+
+ if cmd_res["passed"]:
+ res = "PASS"
+ else:
+ res = "FAIL"
+ result_el.setAttribute("result", res)
+
+ if "msg" in cmd_res and cmd_res["msg"]:
+ msg_el = doc.createElement("message")
+ msg_text = doc.createTextNode(cmd_res["msg"])
+ msg_el.appendChild(msg_text)
+ result_el.appendChild(msg_el)
+
+ if "res_data" in cmd_res and cmd_res["res_data"]:
+ res_data_el = doc.createElement("result_data")
+ serialize_obj(cmd_res["res_data"], doc, res_data_el)
+ command_el.appendChild(res_data_el)
+ return doc.toprettyxml()
10 years, 6 months
[lnst] NetTestController: accept new result dictionaries
by Jiří Pírko
commit 47ea76da28213d999b2cb17b79bbe884a7ff37db
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 4 13:36:04 2013 +0200
NetTestController: accept new result dictionaries
The controller will now use the new format of the command result
dictionaries and print them to the logs during recipe execution.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/NetTestController.py | 19 ++++++++++++-------
1 files changed, 12 insertions(+), 7 deletions(-)
---
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py
index a5b2c2f..c7d1fab 100644
--- a/lnst/Controller/NetTestController.py
+++ b/lnst/Controller/NetTestController.py
@@ -35,6 +35,7 @@ from lnst.Common.ConnectionHandler import send_data, recv_data
from lnst.Common.ConnectionHandler import ConnectionHandler
from lnst.Common.Config import lnst_config
from lnst.Common.RecipePath import RecipePath
+from lnst.Common.Colours import decorate_with_preset
import lnst.Controller.Task as Task
class NetTestError(Exception):
@@ -508,13 +509,17 @@ class NetTestController:
if self._res_serializer:
self._res_serializer.add_cmd_result(command, cmd_res)
- logging.debug("Result: %s", str(cmd_res))
- if "res_data" in cmd_res:
- res_data = pformat(cmd_res["res_data"])
- logging.info("Result data: %s", (res_data))
- if not cmd_res["passed"]:
- logging.error("Command failed: [%s], Error message: \"%s\"",
- str_command(command), cmd_res["err_msg"])
+ if cmd_res["passed"]:
+ res_str = decorate_with_preset("PASS", "pass")
+ else:
+ res_str = decorate_with_preset("FAIL", "fail")
+ logging.info("Result: %s" % res_str)
+ if "report" in cmd_res and cmd_res["report"] != "":
+ logging.info("Result data:")
+ for line in cmd_res["report"].splitlines():
+ logging.info(4*" " + line)
+ if "msg" in cmd_res and cmd_res["msg"] != "":
+ logging.info("Status message from slave: \"%s\"" % cmd_res["msg"])
return cmd_res
10 years, 6 months
[lnst] Machine: remove expect checking
by Jiří Pírko
commit 7d07a78ed36920c1cbebb2d90d76114404c94b7e
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 4 13:36:03 2013 +0200
Machine: remove expect checking
The expect value is now checked on the slave.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/Machine.py | 7 -------
1 files changed, 0 insertions(+), 7 deletions(-)
---
diff --git a/lnst/Controller/Machine.py b/lnst/Controller/Machine.py
index f773bdb..f212824 100644
--- a/lnst/Controller/Machine.py
+++ b/lnst/Controller/Machine.py
@@ -198,13 +198,6 @@ class Machine(object):
signal.alarm(0)
signal.signal(signal.SIGALRM, prev_handler)
- # FIXME: This shouldn't be here!
- if "expect" in command:
- cmd_res["passed"] = cmd_res["passed"] == command["expect"]
- if not cmd_res["passed"]:
- msg = "The result differs from what was expected."
- cmd_res["err_msg"] = msg
-
return cmd_res
def get_hostname(self):
10 years, 6 months
[lnst] NetTestCommand: fix control commands
by Jiří Pírko
commit 899aced48f707df078f8e299221e4d990c326af4
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 4 13:36:02 2013 +0200
NetTestCommand: fix control commands
Control commands need to be changed because of the previous commit.
Without this change the result summary would contain the incorrect
result header line for commands that were interrupted or killed before
finishing but would display information correctly when the command
finished before the control command.
The changes made in this commit ensure that the header line is
reformatted when appropriate.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Common/NetTestCommand.py | 20 +++++++++++++-------
lnst/Slave/NetTestSlave.py | 6 +++---
2 files changed, 16 insertions(+), 10 deletions(-)
---
diff --git a/lnst/Common/NetTestCommand.py b/lnst/Common/NetTestCommand.py
index da763f9..9131597 100644
--- a/lnst/Common/NetTestCommand.py
+++ b/lnst/Common/NetTestCommand.py
@@ -88,6 +88,7 @@ class NetTestCommand:
self._connection_pipe = None
self._killed = False
self._finished = False
+ self._control_cmd= None
self._result = None
self._log_ctl = log_ctl
@@ -163,17 +164,19 @@ class NetTestCommand:
def join(self):
self._process.join()
- def wait_for(self):
+ def wait_for(self, cmd):
logging.debug("Waiting for background command with id \"%s\", pid \"%d\"" % (self._id, self._pid))
self._finished = True
+ self._control_cmd = cmd
- def interrupt(self):
+ def interrupt(self, cmd):
self._finished = True
if os.path.exists("/proc/%d" % self._pid):
logging.debug("Interrupting background command with id \"%s\", pid \"%d\"" % (self._id, self._pid))
os.killpg(os.getpgid(self._pid), signal.SIGINT)
+ self._control_cmd = cmd
- def kill(self):
+ def kill(self, cmd):
if os.path.exists("/proc/%d" % self._pid):
if self._id:
logging.debug("Killing background command with id \"%s\", pid \"%d\"" % (self._id, self._pid))
@@ -182,6 +185,7 @@ class NetTestCommand:
self._killed = True
os.killpg(os.getpgid(self._pid), signal.SIGKILL)
self._process.join()
+ self._control_cmd = cmd
def get_result(self):
if self._killed:
@@ -194,6 +198,8 @@ class NetTestCommand:
return result
def set_result(self, result):
+ if self._control_cmd != None:
+ result["res_header"] = self._control_cmd._format_cmd_res_header()
self._result = result
def get_connection_pipe(self):
@@ -217,7 +223,7 @@ class NetTestCommandContext:
def _kill_all_cmds(self):
for id in self._dict:
- self._dict[id].kill()
+ self._dict[id].kill(None)
def cleanup(self):
logging.debug("Cleaning up leftover processes.")
@@ -425,7 +431,7 @@ class NetTestCommandWait(NetTestCommandControl):
def run(self):
bg_id = self._command["proc_id"]
bg_cmd = self._command_context.get_cmd(bg_id)
- bg_cmd.wait_for()
+ bg_cmd.wait_for(self)
result = bg_cmd.get_result()
if result != None:
bg_cmd.join()
@@ -437,7 +443,7 @@ class NetTestCommandIntr(NetTestCommandControl):
def run(self):
bg_id = self._command["proc_id"]
bg_cmd = self._command_context.get_cmd(bg_id)
- bg_cmd.interrupt()
+ bg_cmd.interrupt(self)
result = bg_cmd.get_result()
if result != None:
bg_cmd.join()
@@ -449,7 +455,7 @@ class NetTestCommandKill(NetTestCommandControl):
def run(self):
bg_id = self._command["proc_id"]
bg_cmd = self._command_context.get_cmd(bg_id)
- bg_cmd.kill()
+ bg_cmd.kill(self)
result = bg_cmd.get_result()
if result != None:
bg_cmd.join()
diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py
index 641b83b..94a92c0 100644
--- a/lnst/Slave/NetTestSlave.py
+++ b/lnst/Slave/NetTestSlave.py
@@ -281,7 +281,7 @@ class SlaveMethods:
def kill_command(self, id):
cmd = self._command_context.get_cmd(id)
- cmd.kill()
+ cmd.kill(None)
self._command_context.del_cmd(cmd)
return True
@@ -530,11 +530,11 @@ class NetTestSlave:
cmd.join()
del msg["cmd_id"]
+ cmd.set_result(msg["result"])
if cmd.finished():
+ msg["result"] = cmd.get_result()
self._server_handler.send_data_to_ctl(msg)
self._cmd_context.del_cmd(cmd)
- else:
- cmd.set_result(msg["result"])
elif msg["type"] == "netlink":
for sub_msg in msg["data"]:
if sub_msg["event"] == "RTM_NEWLINK":
10 years, 6 months
[lnst] NetTestCommand: command result interface change
by Jiří Pírko
commit f8bd7aa734d89a3c480f05b1c2f20ae5691acc1a
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 4 13:36:01 2013 +0200
NetTestCommand: command result interface change
This commit changes the way commands report their results. The
NetTestCommandGeneric class no longer defines a set_result function, and
instead set_pass or set_fail functions should be used. These functions
create the result object (a dictionary) that contains relevant fields.
The functions also check the expect property of the command potentially
inverting the result of the command, with a relevant message added.
Previously there was an option to supply a message when calling the
set_pass or set_fail method from a test module. This was removed and
instead these messages should be moved into the res_data object of the
test module.
The main reason for this was to move the checking of expect to the slave
and to also move res_data formatting to the slave.
Both set_ functions accept a parameter that contains result data of the
command. This should be a dictionary that can be serialized into a valid
xml document and a check function for this will be added later.
The supplied res_data is formatted using the function format_res_data
which can be overridden in the subclasses if the user wants to customize
the formatting.
The function _format_cmd_res_header is used to format the first line of
the command summary, which differs by command type. This shouldn't be
overridden when implementing a custom test module.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Common/NetTestCommand.py | 100 +++++++++++++++++++++++++++++-----
lnst/Common/TestsCommon.py | 26 ++++------
lnst/Controller/NetTestController.py | 6 ++-
3 files changed, 101 insertions(+), 31 deletions(-)
---
diff --git a/lnst/Common/NetTestCommand.py b/lnst/Common/NetTestCommand.py
index 1d50c7c..da763f9 100644
--- a/lnst/Common/NetTestCommand.py
+++ b/lnst/Common/NetTestCommand.py
@@ -126,7 +126,9 @@ class NetTestCommand:
else:
logging.debug("Running in background with"
" bg_id \"%s\" pid \"%d\"" % (self._id, self._pid))
- return {"passed": True}
+ return {"passed": True,
+ "res_header": self._cmd_cls._format_cmd_res_header(),
+ "msg": "Running in background."}
def _run(self):
os.setpgrp()
@@ -184,8 +186,8 @@ class NetTestCommand:
def get_result(self):
if self._killed:
result = {}
- result["type"] = "result"
result["passed"] = True
+ result["msg"] = "Command killed."
else:
result = self._result
@@ -251,9 +253,6 @@ class NetTestCommandGeneric:
def run(self):
pass
- def set_result(self, result):
- self._result = result
-
def get_result(self):
if not self._result:
'''
@@ -263,16 +262,55 @@ class NetTestCommandGeneric:
self.set_pass()
return self._result
- def set_fail(self, err_msg):
- result = {"passed": False, "err_msg": err_msg}
- self.set_result(result)
+ def set_fail(self, res_data=None):
+ res = False
+ msg = ""
+ if "expect" in self._command and self._command["expect"] == False:
+ res = True
+ msg = "Command failed, as was specified in the recipe."
+ result = {"passed": res,
+ "res_data": res_data,
+ "msg": msg,
+ "report": self.format_res_data(res_data),
+ "res_header": self._format_cmd_res_header()}
+ self._result = result
return result
- def set_pass(self):
- result = {"passed": True}
- self.set_result(result)
+ def set_pass(self, res_data=None):
+ res = True
+ msg = ""
+ if "expect" in self._command and self._command["expect"] == False:
+ res = False
+ msg = "Command expected to fail, but passed!"
+ result = {"passed": res,
+ "res_data": res_data,
+ "msg": msg,
+ "report": self.format_res_data(res_data),
+ "res_header": self._format_cmd_res_header()}
+ self._result = result
return result
+ def format_res_data(self, res_data, level=0):
+ formatted_data = ""
+ if res_data:
+ max_key_len = 0
+ for key in res_data.keys():
+ if len(key) > max_key_len:
+ max_key_len = len(key)
+ for key, value in res_data.iteritems():
+ if type(value) == dict:
+ formatted_data += level*4*" " + str(key) + ":\n"
+ formatted_data += self.format_res_data(value, level+1)
+ else:
+ formatted_data += level*4*" " + str(key) + ":" + \
+ (max_key_len-len(key))*" " + \
+ "\t" + str(value) + "\n"
+
+ return formatted_data
+
+ def _format_cmd_res_header(self):
+ return "%-14s" % self._command["type"]
+
def set_handle_intr(self):
pass
@@ -305,6 +343,18 @@ class NetTestCommandExec(NetTestCommandGeneric):
else:
self.set_fail("Command failed to execute")
+ def _format_cmd_res_header(self):
+ cmd_type = self._command["type"]
+ cmd_val = self._command["command"]
+
+ if "bg_id" in self._command:
+ bg_id = "bg_id: %s" % self._command["bg_id"]
+ else:
+ bg_id = ""
+
+ cmd = "%-14s%s%s" %(cmd_type, cmd_val, bg_id)
+ return cmd
+
class NetTestCommandConfig(NetTestCommandGeneric):
def _retrive_option(self, option):
cmd_str = "cat %s" % option
@@ -343,15 +393,34 @@ class NetTestCommandConfig(NetTestCommandGeneric):
"current_val": value,
"previous_val": prev_val})
- res = {"passed": True}
- res["res_data"] = res_data
- return res
+ self.set_pass(res_data)
+ return self.get_result()
+
+ def format_res_data(self, res_data, level=0):
+ formatted_data = ""
+ max_name_len = 0
+ for option in res_data["options"]:
+ if len(option["name"]) > max_name_len:
+ max_name_len = len(option["name"])
+ for option in res_data["options"]:
+ vals = "previous: %s current: %s" % (option["previous_val"],
+ option["current_val"])
+ formatted_data += 4*level*" " + option["name"] + \
+ (max_name_len - len(option["name"]))*" " + \
+ "\t" + vals + "\n"
+ return formatted_data
class NetTestCommandControl(NetTestCommandGeneric):
def __init__(self, command_context, command):
self._command_context = command_context
NetTestCommandGeneric.__init__(self, command)
+ def _format_cmd_res_header(self):
+ cmd_type = self._command["type"]
+ cmd_val = self._command["proc_id"]
+ cmd = "%-14s id: %s" % (cmd_type, cmd_val)
+ return cmd
+
class NetTestCommandWait(NetTestCommandControl):
def run(self):
bg_id = self._command["proc_id"]
@@ -361,6 +430,7 @@ class NetTestCommandWait(NetTestCommandControl):
if result != None:
bg_cmd.join()
self._command_context.del_cmd(bg_cmd)
+ result["res_header"] = self._format_cmd_res_header()
return result
class NetTestCommandIntr(NetTestCommandControl):
@@ -372,6 +442,7 @@ class NetTestCommandIntr(NetTestCommandControl):
if result != None:
bg_cmd.join()
self._command_context.del_cmd(bg_cmd)
+ result["res_header"] = self._format_cmd_res_header()
return result
class NetTestCommandKill(NetTestCommandControl):
@@ -383,6 +454,7 @@ class NetTestCommandKill(NetTestCommandControl):
if result != None:
bg_cmd.join()
self._command_context.del_cmd(bg_cmd)
+ result["res_header"] = self._format_cmd_res_header()
return result
def get_command_class(command_context, command, resource_table):
diff --git a/lnst/Common/TestsCommon.py b/lnst/Common/TestsCommon.py
index 38c3287..3008e81 100644
--- a/lnst/Common/TestsCommon.py
+++ b/lnst/Common/TestsCommon.py
@@ -84,22 +84,6 @@ class TestGeneric(NetTestCommandGeneric):
continue
break
- def set_fail(self, err_msg, res_data = None):
- self._testLogger.error("FAILED - %s" % err_msg)
- res = {"passed": False, "err_msg": err_msg}
- if res_data:
- res["res_data"] = res_data
- self.set_result(res)
- return res
-
- def set_pass(self, res_data = None):
- self._testLogger.debug("PASSED")
- res = {"passed": True}
- if res_data:
- res["res_data"] = res_data
- self.set_result(res)
- return res
-
def _get_val(self, value, opt_type, default):
if opt_type == "addr":
'''
@@ -154,3 +138,13 @@ class TestGeneric(NetTestCommandGeneric):
This should be used to get mandatory multi options (array of values)
'''
return self.get_multi_opt(name, mandatory=True, opt_type=opt_type)
+
+ def _format_cmd_res_header(self):
+ cmd_val = self._command["module"]
+ cmd_type = self._command["type"]
+ if "bg_id" in self._command:
+ bg_id = " bg_id: %s" % self._command["bg_id"]
+ else:
+ bg_id = ""
+ cmd = "%-14s%s%s" %(cmd_type, cmd_val, bg_id)
+ return cmd
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py
index 33ead31..a5b2c2f 100644
--- a/lnst/Controller/NetTestController.py
+++ b/lnst/Controller/NetTestController.py
@@ -489,7 +489,11 @@ class NetTestController:
if command["type"] == "ctl_wait":
sleep(command["seconds"])
- cmd_res = {"passed" : True}
+ cmd_res = {"passed": True,
+ "res_header": "%-14s%ss" % ("ctl_wait",
+ command["seconds"]),
+ "msg": "",
+ "res_data": None}
return cmd_res
machine_id = command["machine"]
10 years, 6 months
[lnst] NetTestResultSerializer: rename cmd_seq to task
by Jiří Pírko
commit f9028fd6d716231c5b8f28b3f146c50d049f1e6a
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Wed Sep 4 13:36:00 2013 +0200
NetTestResultSerializer: rename cmd_seq to task
Since we changed command_sequences to tasks in xml, this commit changes
the summary as well.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/NetTestResultSerializer.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
---
diff --git a/lnst/Controller/NetTestResultSerializer.py b/lnst/Controller/NetTestResultSerializer.py
index ffb5ab4..d245568 100644
--- a/lnst/Controller/NetTestResultSerializer.py
+++ b/lnst/Controller/NetTestResultSerializer.py
@@ -126,7 +126,7 @@ class NetTestResultSerializer:
seq_num = 1
for cmd_seq in recipe.getElementsByTagName("command_sequence"):
- command_sequence = 4*" "+"cmd_seq: %s" % seq_num
+ command_sequence = 4*" "+"task: %s" % seq_num
output_pairs.append((command_sequence, ""))
seq_num = seq_num + 1
10 years, 6 months
[PATCH] RecipeParser: Support comments in tasks
by Radek Pazdera
There is the bug in the way the <task> tags were processed. The
processor didn't ignore comments and considered them to be invalid
commands.
This commit fixes that.
Reported-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
---
lnst/Controller/RecipeParser.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py
index 9bebe8c..9311b4a 100644
--- a/lnst/Controller/RecipeParser.py
+++ b/lnst/Controller/RecipeParser.py
@@ -14,6 +14,7 @@ import logging
import os
import re
import sys
+from lxml import etree
from lnst.Common.Config import lnst_config
from lnst.Common.NetUtils import normalize_hwaddr
from lnst.Common.Utils import bool_it
@@ -157,6 +158,10 @@ class RecipeParser(XmlParser):
if len(task_tag) > 0:
task["commands"] = XmlCollection(task_tag)
for cmd_tag in task_tag:
+ # Ignore comments
+ if cmd_tag.tag is etree.Comment:
+ continue
+
if cmd_tag.tag == "run":
cmd = self._process_run_cmd(cmd_tag)
elif cmd_tag.tag == "config":
--
1.8.3.1
10 years, 6 months
[lnst] RecipeParser: Support comments in tasks
by Jiří Pírko
commit 650c34dace8b55a283dea837e782b0b5d1be3d70
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Wed Sep 4 11:08:13 2013 +0200
RecipeParser: Support comments in tasks
There is the bug in the way the <task> tags were processed. The
processor didn't ignore comments and considered them to be invalid
commands.
This commit fixes that.
Reported-by: Ondrej Lichtner <olichtne(a)redhat.com>
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/RecipeParser.py | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
---
diff --git a/lnst/Controller/RecipeParser.py b/lnst/Controller/RecipeParser.py
index 9bebe8c..9311b4a 100644
--- a/lnst/Controller/RecipeParser.py
+++ b/lnst/Controller/RecipeParser.py
@@ -14,6 +14,7 @@ import logging
import os
import re
import sys
+from lxml import etree
from lnst.Common.Config import lnst_config
from lnst.Common.NetUtils import normalize_hwaddr
from lnst.Common.Utils import bool_it
@@ -157,6 +158,10 @@ class RecipeParser(XmlParser):
if len(task_tag) > 0:
task["commands"] = XmlCollection(task_tag)
for cmd_tag in task_tag:
+ # Ignore comments
+ if cmd_tag.tag is etree.Comment:
+ continue
+
if cmd_tag.tag == "run":
cmd = self._process_run_cmd(cmd_tag)
elif cmd_tag.tag == "config":
10 years, 6 months