Merge ~ycheng-twn/checkbox-ng:0001_checkbox_cli_show into checkbox-ng:master
- Git
- lp:~ycheng-twn/checkbox-ng
- 0001_checkbox_cli_show
- Merge into master
Status: | Rejected |
---|---|
Rejected by: | Yuan-Chen Cheng |
Proposed branch: | ~ycheng-twn/checkbox-ng:0001_checkbox_cli_show |
Merge into: | checkbox-ng:master |
Diff against target: |
127 lines (+94/-0) (has conflicts) 2 files modified
checkbox_ng/launcher/checkbox_cli.py (+1/-0) checkbox_ng/launcher/subcommands.py (+93/-0) Conflict in checkbox_ng/launcher/subcommands.py |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Maciej Kisielewski (community) | Needs Fixing | ||
Review via email:
|
Commit message
add show subcommand to checkbox_cli.
It output the full job or job template so that we don't
need to visit the job definition file.
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Yuan-Chen Cheng (ycheng-twn) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Yuan-Chen Cheng (ycheng-twn) wrote : | # |
sample outout for job template
$ checkbox-cli show com.canonical.
origin: /usr/share/
unit: template
template_resource: device
template_filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN'
full_id: com.canonical.
id: com.canonical.
_summary: Wake on Wireless LAN (WoWLAN) test from S5 - {interface} - wakeonlan
_purpose: Check that another system can wake up from S5 the SUT using WoWLAN function.
_steps:
1. Ensure WoWLAN is enabled in BIOS.
2. Initiate connection to an AP (using nmcli)
3. Configure the device for WoWLAN, run the command:
$ sudo iw phy phy0 wowlan enable magic-packet
4. Press Enter for S5 (Soft Off).
5. From another computer on the same network run the following command:
$ wakeonlan {mac}
If wakeonlan tool is not installed run:
$ sudo apt install wakeonlan
6. Resume Checkbox
_verification: Did the SUT wake up from S5?
plugin: user-interact-
command: poweroff
user: root
category_id: com.canonical.
estimated_duration: 120
template_unit: job
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Yuan-Chen Cheng (ycheng-twn) wrote : | # |
As use sideload, sample output:
$ checkbox-cli show com.canonical.
Using sideloaded provider: plainbox-
origin: /var/tmp/
id: com.canonical.
partial_id: 6lowpan/kconfig
summary: kernel config options for 6LoWPAN
description: Checks the kernel config options for 6LoWPAN / IEEE802.15.4 support
plugin: shell
command:
for config in CONFIG_6LOWPAN CONFIG_IEEE802154 CONFIG_
grep -E "$config=(y|m)" /boot/config-
done
estimated_duration: 1.2
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Maciej Kisielewski (kissiel) wrote : | # |
The branch uses unsupported syntax - Xenial runs python 3.5 which doesn't support f-strings.
Also worth noting is that a similar information is printed with:
checkbox-cli list -a all_jobs (also prints info about templates)
and
checkbox-cli list -a job (prints info only about concrete jobs)
Also, check the comments below.
Functions defined in this MR are easy to be tested with UTs.
- 3e1e841... by Yuan-Chen Cheng
-
clean up and modify accoording comment from MP.
Test this version in ubuntu:xenial and jammy, and both passed.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Yuan-Chen Cheng (ycheng-twn) wrote : | # |
Modify code per comment from Maciej, and clean up a bit.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Maciej Kisielewski (kissiel) wrote : | # |
Thank you for all the improvements!
I know I should have asked this earlier, but why only some parts of the records are printed?
Printing all of them would be so much simpler and possibly would carry more information with it.
I also found possible problems/room for improvement in the command's positional argument(s) handling.
If the code requires an argument then just remove the `nargs='?'` from the add_argument invocation. The whole branch from the invoked() method can go away as argparse would cover it for you. Also, if calling that command without an argument is wrong then it should not return None (which ends up being return code of 0).
As for the nargs, I'd prefer to see '+' there, as it means there must be at least one, while giving the user option to specify more than one name. This would be stored as a list so it'd be super easy to iterate over.
`attr1` and `attr2` don't really communicate their purposes.
The recursive part is not trivial and could use some comment or a docstring.
IMHO the search_and_show function has too many responsibilities:
* it defines the "filters" - which are constant, but are defined in a function that's called recursively, which introduces unnecessary performance penalty
* it understands what the object it searches for is
* traverses recursively the object tree
* it calls the printing function (increasing the complexity) and making it harder to test (maybe that's why there's no tests in this MR)
IMHO much clearer (and less complex) structure would be following set of functions and their respective responsibilities:
print_concrete_
print_template() - self explanatory
find_object(
Taken into consideration the `nargs='+'` advice the find_object could take an iterable of names enabling the name check to work on the whole list (which would be faster than searching for object while iterating over list of names - recursive tree traversal is much more expensive than checking if string is in a relatively short list of strings)
- 89c7ed7... by Yuan-Chen Cheng
-
refactoring and output by origin lines first, and then all attrs
- 9ee651c... by Yuan-Chen Cheng
-
break check to save some interation
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Maciej Kisielewski (kissiel) wrote : | # |
There's a few problems with the code. See below.
It still feels like the complexity of this implementation is unnecessarily high.
The whole implementation could be roughly ~20LOC. And because you probably want to land this soon, and my reviews introduce delays, let me share a simpler version of how this could be implemented.
class Show():
def register_
'IDs', nargs='+', help=_("Show the definitions of objects"))
def invoked(self, ctx):
providers = ctx.sa.
root = Explorer(
def _traverse_
if obj.name in self._searched_
for child in obj.children:
def _print_obj(self, obj):
for k,v in obj.attrs.items():
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Maciej Kisielewski (kissiel) wrote : | # |
The implementation I provided still needs following things:
* better help string
* docstrings
* tests
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Yuan-Chen Cheng (ycheng-twn) wrote : | # |
As the sample code provided use the value from obj.attr. Per previous
test and check, it does not have all the important fields I'd like to
see in the output for job template.
Ex: (With the code provided)
$ /usr/local/
Using sideloaded provider: plainbox-
id: com.canonical.
partial_id: wireless/
template_unit: job
template_resource: device
template_filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN'
template_imports: None
origin: /var/tmp/
You can see that it miss certain important value like the command.
While the full comment for it is:
unit: template
template-resource: device
template-filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN'
id: wireless/
_summary: Wake on Wireless LAN (WoWLAN) test from S5 - {interface} - wakeonlan
_purpose:
Check that another system can wake up from S5 the SUT using WoWLAN function.
_steps:
1. Ensure WoWLAN is enabled in BIOS.
2. Initiate connection to an AP (using nmcli)
3. Configure the device for WoWLAN, run the command:
$ sudo iw phy phy0 wowlan enable magic-packet
4. Press Enter for S5 (Soft Off).
5. From another computer on the same network run the following command:
$ wakeonlan {mac}
If wakeonlan tool is not installed run:
$ sudo apt install wakeonlan
6. Resume Checkbox
_verification:
Did the SUT wake up from S5?
plugin: user-interact-
command: poweroff
user: root
category_id: com.canonical.
estimated_duration: 120
flags: preserve-locale
If we use obj._impl.
That's why I think just using the exact content from the origin file, with it's
existing format and data order, which will be not only the best quality in general,
(formatting, order of fields) but also we have the shortest code needed to have a good output.
The other way is to make sure all data field exists in obj.attr, which need more code change if I see it correctly.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Maciej Kisielewski (kissiel) (last edit ): | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Maciej Kisielewski (kissiel) wrote : | # |
(previous comment deleted as LP deleted indentation in the code)
My knee-jerk reaction for re-reading file is to say it's inefficient and we shouldn't do it, but then, we only have to do it once (and one file) per listed unit. This means that when listing one unit, only one extra file open is done, which is bearable. In other words, you're absolutely right, at this stage it's the cleanest solution.
Because there's a concept of 'virtual' units, possibly not containing origin, we must be catch the potential problems stemming from that, so I propose the print to look something like this:
def _print_obj(self, obj):
try:
path, line_range = obj.attrs[
with open(path, 'rt', encoding='utf-8') as pxu:
# origin uses human-like numbering (starts with 1), so we need
# to substract 1. The range in origin is inclusive,
# so the end_index is right
record = pxu.readlines(
except (ValueError, KeyError):
except OSError as exc:
path, obj.attrs['id']))
PS Shame that `attrs` is so limited for templates (broken?:)).
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Yuan-Chen Cheng (ycheng-twn) wrote : | # |
Unmerged commits
- 4efe835... by Yuan-Chen Cheng
-
m
- 9ee651c... by Yuan-Chen Cheng
-
break check to save some interation
- 89c7ed7... by Yuan-Chen Cheng
-
refactoring and output by origin lines first, and then all attrs
- 3e1e841... by Yuan-Chen Cheng
-
clean up and modify accoording comment from MP.
Test this version in ubuntu:xenial and jammy, and both passed. - 553ee38... by Yuan-Chen Cheng
-
add show to checkbox_cli that can display a full job or template.
Preview Diff
1 | diff --git a/checkbox_ng/launcher/checkbox_cli.py b/checkbox_ng/launcher/checkbox_cli.py |
2 | index 8df0bd0..646e00e 100644 |
3 | --- a/checkbox_ng/launcher/checkbox_cli.py |
4 | +++ b/checkbox_ng/launcher/checkbox_cli.py |
5 | @@ -61,6 +61,7 @@ def main(): |
6 | 'launcher': Launcher, |
7 | 'list': List, |
8 | 'run': Run, |
9 | + 'show': Show, |
10 | 'startprovider': StartProvider, |
11 | 'submit': Submit, |
12 | 'show': Show, |
13 | diff --git a/checkbox_ng/launcher/subcommands.py b/checkbox_ng/launcher/subcommands.py |
14 | index a5bf6fd..a8b2c33 100644 |
15 | --- a/checkbox_ng/launcher/subcommands.py |
16 | +++ b/checkbox_ng/launcher/subcommands.py |
17 | @@ -806,6 +806,24 @@ class List(): |
18 | print_objs(ctx.args.GROUP, ctx.sa, ctx.args.attrs) |
19 | |
20 | |
21 | +class Show(): |
22 | + def register_arguments(self, parser): |
23 | + parser.add_argument( |
24 | + 'NAMES', nargs='+', |
25 | + help=_("Show the jobs or objects with the names provided")) |
26 | + |
27 | + @property |
28 | + def sa(self): |
29 | + return self.ctx.sa |
30 | + |
31 | + def invoked(self, ctx): |
32 | + if ctx.args.NAMES is None: |
33 | + print("You need to provide at least a job or a object name as the argument.") |
34 | + return |
35 | + job_name_set = set(ctx.args.NAMES) |
36 | + show_objs(ctx.sa, job_name_set) |
37 | + |
38 | + |
39 | class ListBootstrapped(): |
40 | @property |
41 | def sa(self): |
42 | @@ -932,6 +950,7 @@ def print_objs(group, sa, show_attrs=False, filter_fun=None): |
43 | _show(obj, "") |
44 | |
45 | |
46 | +<<<<<<< checkbox_ng/launcher/subcommands.py |
47 | class Show(): |
48 | def register_arguments(self, parser): |
49 | parser.add_argument( |
50 | @@ -970,3 +989,77 @@ class Show(): |
51 | # provider and service does not have origin |
52 | for k,v in obj.attrs.items(): |
53 | print("{}: {}".format(k, v)) |
54 | +======= |
55 | +def find_objects(target_obj, obj_name_set): |
56 | + """ |
57 | + Resursively find obj and its children any obj with name in the |
58 | + obj_name_set. |
59 | + |
60 | + Note: We will remove the found object name from the obj_name_set. |
61 | + If you want keep the original job_name_set, make |
62 | + a copy of the job_name_set before you call this function. |
63 | + """ |
64 | + output = dict() |
65 | + |
66 | + if (target_obj.name in obj_name_set): |
67 | + output[target_obj.name] = target_obj |
68 | + obj_name_set.remove(target_obj.name) |
69 | + if obj_name_set == 0: |
70 | + return output |
71 | + |
72 | + for child in target_obj.children: |
73 | + output = {**output, **(find_objects(child, obj_name_set))} |
74 | + |
75 | + return output |
76 | + |
77 | + |
78 | +def show_objs(sa, job_name_set): |
79 | + providers = sa.get_selected_providers() |
80 | + obj = Explorer(providers).get_object_tree() |
81 | + |
82 | + job_obj_dict = find_objects(obj, job_name_set.copy()) |
83 | + |
84 | + def get_origin_line_range(origin): |
85 | + try: |
86 | + line_range = origin.split(":")[-1] |
87 | + origin_filename = origin[: - len(line_range) - 1] |
88 | + range_ar = list(map(int, line_range.split("-"))) |
89 | + return {"filename": origin_filename, "range":range_ar} |
90 | + except: |
91 | + return {} |
92 | + |
93 | + def print_file_range(file, file_range): |
94 | + with open(file) as of: |
95 | + line_no = 0 |
96 | + for line in of: |
97 | + line_no += 1 |
98 | + if file_range[0] <= line_no and line_no <= file_range[1]: |
99 | + print(line, end="") |
100 | + if line_no > file_range[1]: |
101 | + break |
102 | + |
103 | + def show_obj(obj): |
104 | + output_by_origin = False |
105 | + if obj.attrs.get("origin", None) is not None: |
106 | + print("origin:", obj.attrs.get("origin")) |
107 | + |
108 | + origin = obj.attrs.get("origin") |
109 | + origin_range_info = get_origin_line_range(origin) |
110 | + if "filename" in origin_range_info: |
111 | + print_file_range( |
112 | + origin_range_info["filename"], |
113 | + origin_range_info["range"]) |
114 | + output_by_origin = True |
115 | + |
116 | + if not output_by_origin: |
117 | + print(obj.group + ":", obj.name) |
118 | + for key, item in obj.attrs.items(): |
119 | + print("{}: {}".format(key, item)) |
120 | + |
121 | + if len(job_obj_dict) == 0: |
122 | + raise SystemExit("Can't find any in {}.".format(job_name_set)) |
123 | + |
124 | + for _key, obj_value in job_obj_dict.items(): |
125 | + print("=" * 60) |
126 | + show_obj(obj_value) |
127 | +>>>>>>> checkbox_ng/launcher/subcommands.py |
sample output for job
$ checkbox-cli show com.canonical. certification: :rtc plainbox- provider- resource- generic/ jobs/resource. pxu:282- 292 certification: :rtc
origin: /usr/share/
id: com.canonical.
partial_id: rtc
summary: Creates resource info for RTC
plugin: resource
command:
if [ -e /proc/driver/rtc ]
then
echo "state: supported"
else
echo "state: unsupported"
fi
estimated_duration: 0.02