ListCmd.py: Make Rows sortable

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2022-12-15 16:17:47 +01:00
commit dc40decb96

View file

@ -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):