From dc40decb96528272bcf4fbfb5d37ccd0c0fb11b3 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Thu, 15 Dec 2022 16:17:47 +0100 Subject: [PATCH] ListCmd.py: Make Rows sortable Signed-off-by: Jan Lindemann --- src/python/devtest/os/test/ListCmd.py | 137 +++++++++++++++++++++----- 1 file changed, 111 insertions(+), 26 deletions(-) diff --git a/src/python/devtest/os/test/ListCmd.py b/src/python/devtest/os/test/ListCmd.py index 338f4e4..f960f83 100644 --- a/src/python/devtest/os/test/ListCmd.py +++ b/src/python/devtest/os/test/ListCmd.py @@ -3,7 +3,10 @@ import re import os import asyncio +import shlex +import traceback from operator import itemgetter +from functools import total_ordering from jwutils.log import * from jwutils import Options from devtest.os import * @@ -14,21 +17,30 @@ class ListCmd(TestCase): # export # ------------------------------------- class Row + @total_ordering class Row: - def field(self, key, default=None): + def field(self, key, default=None, throw=True): if key in self.__fields.keys(): return self.__fields[key] - if default is not None: + if default is not None or not throw: return default - raise KeyError('No field "{}" in row "{}"'.format(key, self)) + raise KeyError('No field "{}" in row "{}"'.format(key, self.__fields)) - def attrib(self, key, default=None): + def attrib(self, key, default=None, throw=True): if self.__attribs is not None: return self.__attribs.get(key, default) - if default is not None: + if default is not None or not throw: return default - raise KeyError('No attrib "{}" in row "{}"'.format(key, self)) + raise KeyError('No attrib "{}" in row "{}"'.format(key, self.__fields)) + + @property + def fields(self): + return self.__fields + + @property + def attribs(self): + return self.__attribs # "needed": [ "dummyd", "v3.23" ] # "key_": [ feature ] @@ -61,18 +73,26 @@ class ListCmd(TestCase): # export def cmp(self, other): decisive = self.parent.decisive for field in decisive: - ret = self.field(field) < other.field(field) - if ret: - return ret + s, o = self.field(field, throw=False), other.field(field, throw=False) + if (s and not o) or (o and not s): + if s: # certainly not o + return 1 + return -1 # certainly not s + if s == o: + continue + if s > o: + return 1 + if o > s: + return -1 return 0 def to_str(self, only_values=False, quotes=None, fields=['fields']): use_fields = None for f_set_name in fields: - use_fields = self.parent.row_info(f_set_name, default=False) - if use_fields != False: + use_fields = self.parent.row_info(f_set_name, throw=False) + if use_fields is not None: break - if use_fields == False: + if use_fields is None: raise Exception("None of the fields wanted for formatting are available: {}".format(fields)) q = '"' if quotes == True else ('' if quotes is None and only_values else '') if only_values: @@ -83,10 +103,37 @@ class ListCmd(TestCase): # export r += " | " + str(self.__attribs) return r + def dump(self, prio, msg=None, **kwargs): + if 'caller' not in kwargs: + caller = get_caller_pos(1) + else: + caller = kwargs['caller'] + del kwargs['caller'] + if msg is not None: + slog(prio, ',----------------------- {}'.format(msg), caller=caller) + slog(prio, '| line="{}"'.format(self.__line), caller=caller) + slog(prio, '| ---------- types', caller=caller) + for t in self.__types: + slog(prio, '| {}'.format(t), caller=caller) + if len(self.__fields): + slog(prio, '| ---------- fields', caller=caller) + for key, val in self.__fields.items(): + slog(prio, '| {}="{}"'.format(key, val), caller=caller) + if self.__attribs is not None and len(self.__attribs): + slog(prio, '| ---------- attribs', caller=caller) + for key, val in self.__attribs.items(): + slog(prio, '| {}="{}"'.format(key, val), caller=caller) + if msg is not None: + slog(prio, '`----------------------- {}'.format(msg), caller=caller) + @property def name(self): return '(' + self.to_str(fields=['name-fields', 'cmp-fields'], only_values=True) + ')' + @property + def line(self): + return self.__line + def __lt__(self, other): return self.cmp(other) < 0 def __le__(self, other): @@ -103,7 +150,7 @@ class ListCmd(TestCase): # export def __str__(self): return self.to_str() - def _repr__(self): + def __repr__(self): return self.to_str() def __format__(self, fmt): @@ -120,7 +167,7 @@ class ListCmd(TestCase): # export def __hash__(self): decisive = self.parent.decisive - return hash(tuple([self.field(field, '') for field in decisive])) + return hash(tuple([str(self.field(field, '')) for field in decisive])) # ------------------------------------- class ListCmd methods @@ -130,6 +177,7 @@ class ListCmd(TestCase): # export self.total_timeout = total_timeout self.__decisive = None self.__row_info = None + self.__write_raw_response = True if write_response is not None: self.__write_response = write_response else: @@ -161,7 +209,10 @@ class ListCmd(TestCase): # export def _filter(self, output): return output - def row_info(self, key, default=None): + def _row_name(self, row): + return '(' + row.to_str(fields=['name-fields', 'cmp-fields'], only_values=True) + ')' + + def row_info(self, key, default=None, throw=False): if self.__row_info == None: info = self._row_info() if type(info) == dict: @@ -172,7 +223,7 @@ class ListCmd(TestCase): # export for i in range(0, len(info)): self.__row_info[keys[i]] = info[i] if not key in self.__row_info.keys(): - if default is not None: + if default is not None or not throw: return default raise Exception('Required row info "{}" missing'.format(key)) return self.__row_info[key] @@ -208,31 +259,45 @@ class ListCmd(TestCase): # export def _eval(self, output, features, header=None): def format_rows(rows, quotes=False): - def cmp(r1, r2): - for k in sort_keys: - if r1[k] < r2[k]: - return True - return False - + #def cmp(r1, r2): + # for k in sort_keys: + # if r1[k] < r2[k]: + # return True + # return False sort_keys = [] key_sets = ['name-fields', 'cmp-fields'] for s in key_sets: for k in self.row_info(s, []): if k not in sort_keys: sort_keys.append(k) - - #return sorted([ row.to_str(fields=['cmp-fields', 'fields'], only_values=True, quotes=quotes) for row in rows], key=cmp) - return [ row.to_str(fields=['cmp-fields', 'fields'], only_values=True, quotes=quotes) for row in sorted(rows, key=itemgetter(*sort_keys))] + #return [row.to_str(fields=['cmp-fields', 'fields'], only_values=True, quotes=quotes) for row in sorted(rows, key=itemgetter(*sort_keys))] + return [row.to_str(fields=['cmp-fields', 'fields'], only_values=True, quotes=quotes) for row in sorted(rows)] if self.__write_response and not os.path.exists(self.refpath): ref_lines = [] else: + slog(INFO, 'Reading reference from "{}"'.format(self.refpath)) with open(self.refpath, "r") as f: ref_lines = f.readlines() + if self.__write_raw_response: + raw_response_path = self.refpath + '.raw' + with open(raw_response_path, "w") as f: + slog(INFO, 'Writing raw response to "{}"'.format(raw_response_path)) + if header: + f.write(header) + f.write('\n'.join(output)) output = self._filter(output) + last_features = set() if self.__write_response: response_path = self.refpath + '.last' + if os.path.exists(response_path): + with open(response_path, "r") as f: + for line in f: + payload, matches = re.subn('^ *# *features: *', '', line) + if matches > 0: + last_features = set(shlex.split(payload)) + break with open(response_path, "w") as f: slog(INFO, 'Writing response to "{}"'.format(response_path)) if header: @@ -275,6 +340,22 @@ class ListCmd(TestCase): # export if not len(r): return None + feature_diff = set(features) - last_features + if self.__write_response and len(feature_diff): + response_path = self.refpath + '.bad' + feature_diff_str = ', '.join(['"{}"'.format(f) for f in feature_diff]) + with open(response_path, "w") as f: + slog(INFO, 'Writing feature diff to "{}"'.format(response_path)) + if header: + f.write(header) + if len(missing): + f.write("# --- missing {}\n".format(feature_diff)) + for row in missing: + f.write(row.line + ' # "needed": [{}], "bad": ["default"]\n'.format(feature_diff_str)) + if len(too_many): + f.write("# --- too many {}\n".format(feature_diff)) + for row in too_many: + f.write(row.line + ' # "bad" [{}]\n'.format(feature_diff_str)) return ' and '.join(r) async def _run(self, env, machine, phase): @@ -284,7 +365,11 @@ class ListCmd(TestCase): # export total_timeout=self.total_timeout, echo_cmd=False) if output is None: return "Failed to run command: " + cmd - header = '# ' + cmd + '\n' if self.__write_response else None + if not self.__write_response: + header = None + else: + header = '# ' + cmd + '\n' + header += '# features: {}\n'.format(' '.join(env.features)) return self._eval(output, env.features, header=header) def dump(self, prio, *args, **kwargs):