On Tue, Jan 16, 2024 at 05:12:37PM +0100, Peter Krempa wrote:
scripts: Add 'qemu-qmp-replies-tool' script for testing and
modifying data for qemucapabilitiestest
s/qmp-//
+ try:
+ for line in fh:
+ jsonstr += line
+
+ if line == '\n':
+ if command is None:
+ command = json.loads(jsonstr)
+ else:
+ conv.append((command, json.loads(jsonstr)))
+ command = None
+
+ jsonstr = ''
+
+ if command is not None and jsonstr != '':
+ conv.append((command, json.loads(jsonstr)))
+ command = None
+ jsonstr = ''
+
+ except json.decoder.JSONDecodeError as je:
+ raise qrtException("JSON error:\n'%s'\nwhile processing
snippet:\n'%s'" % (je, jsonstr)) from None
I'm not sure this is the most "pythonic" way to use exceptions, but
I'm also not a purist so I don't mind :)
I've never seen the 'raise Foo() from None' pattern though. Can you
explain why it's needed here?
+# Process the replies file programmatically here.
+# The 'conv' argument contains the whole conversation as a list of
+# (command, reply) tuples, where both command and reply are already parsed JSON
+# and thus represented by native python types (dict, list, etc ...)
+#
+# The code below contains a few examples and hints how to use the programatic
+# processing. Do not forget to use '--regenerate' flag to uptate the output
files.
+#
+# Beware that this updates the output file which is used as input for any
+# subsequent re-run of the tool which can re-apply the modification.
+def modify_replies(conv):
+ return # remove this to enable modifications
+ version = None # filled with a dictionary with 'major', 'minor',
'micro' keys
+
+ for (cmd, rep) in conv:
+ if cmd['execute'] == 'query-version':
+ version = rep['return']['qemu']
+ break
+
+ if version is None:
+ raise Exception("'query-version' not found in the .replies
file")
+
+ idx = -1
+ # Find an index of an entry
+ for i in range(len(conv)):
+ (cmd, rep) = conv[i]
+
+ if cmd['execute'] == 'device-list-properties':
+ idx = i
+
+ if idx == -1:
+ raise Exception("entry not found")
+
+ cmd = {'execute': 'device-list-properties',
+ 'arguments': {'typename': 'example-device'}}
+
+ reply_unsupp = {'error': {'class': 'DeviceNotFound',
+ 'desc': "Device 'example-device' not
found"}}
+
+ reply = json.loads('''
+ {
+ "return": [
+ {
+ "name": "dummy_prop",
+ "type": "str"
+ },
+ {
+ "name": "test",
+ "type": "str"
+ }
+ ]
+ }
+ ''')
+
+ if version['major'] >= 8 and version['minor'] > 0:
+ conv.insert(idx, (cmd, reply))
+ else:
+ conv.insert(idx, (cmd, reply_unsupp))
The way these examples are all jumbled together makes it IMO a lot
harder to understand what each is supposed to do, or even where one
ends and the next one starts. I suggest you do something like this
instead:
def modify_replies(conv):
return # no changes
def _modify_replies_example1(conv):
# ...
def _modify_replies_example2(conv):
# ...
+The default mode is validation which checks the following:
+ - each command has a reply and both are valid JSON
+ - numbering of the 'id' field is as expected
+ - the input file has the expected JSON formatting
+
+The tool can be also used to programmaticaly modify the '.replies' file by
+editting the 'modify_replies' method directly in the source, or for
*editing
+args = parser.parse_args()
+
+if not args.replyfile and not args.repliesdir:
+ parser.print_help()
+ exit(1)
This fails if neither repliesfile or repliesdir have been specified,
but will happily proceed if both are present, in which case only the
former will be considered. Please make sure that scenario is also
handled correctly.
+if args.replyfile:
+ if not process_one(args.replyfile, args):
+ fail = True
+else:
+ files = Path(args.repliesdir).glob('*.replies')
+
+ for file in files:
+ if not process_one(str(file), args):
+ fail = True
This can be simplified as
if args.replyfile:
files = [args.replyfile]
else:
files = Path(args.repliesdir).glob('*.replies')
for file in files:
if not process_one(str(file), args):
fail = True
Honestly I would prefer it if the way to make the script process
multiple files was just to list them all on the command line, but I
understand that you need --repliesdir because meson doesn't have a
native way to perform globbing.
--
Andrea Bolognani / Red Hat / Virtualization