On Thu, Mar 7, 2024 at 2:29 PM Rae Moar <rmoar@xxxxxxxxxx> wrote: > > Add ability to parse multiple files. Additionally add the > ability to parse all results in the KUnit debugfs repository. > > How to parse multiple files: > > ./tools/testing/kunit/kunit.py parse results.log results2.log > > How to parse all files in directory: > > ./tools/testing/kunit/kunit.py parse directory_path/* > > How to parse KUnit debugfs repository: > > ./tools/testing/kunit/kunit.py parse debugfs > > For each file, the parser outputs the file name, results, and test > summary. At the end of all parsing, the parser outputs a total summary > line. > > This feature can be easily tested on the tools/testing/kunit/test_data/ > directory. > > Signed-off-by: Rae Moar <rmoar@xxxxxxxxxx> > --- > Changes since v2: > - Fixed bug with input from command line. I changed this to use > input(). Daniel, let me know if this works for you. Oops, sorry for the delay. Hmm, it seems to be treating the stdin lines like file names $ ./tools/testing/kunit/kunit.py parse < ./tools/testing/kunit/test_data/test_config_printk_time.log File path: Could not find [ 0.060000] printk: console [mc-1] enabled Oh, I see, we're prompting the user via input("File path: ") ? I'm not necessarily against such a change, but I would personally prefer the old behavior of being able to read ktap from stdin directly. As a user, I'd also prefer to only type out filenames as arguments where I can get autocomplete, so `input()` here wouldn't help me personally. Applying a hackish patch like this [1] on top gets the behavior I'd personally expect: $ ./tools/testing/kunit/kunit.py parse < ./tools/testing/kunit/test_data/test_config_printk_time.log /dev/stdin ... [16:01:50] Testing complete. Ran 10 tests: passed: 10 I'd mentioned in the previous version that we could have parsed files contain a `Union[str, TextIO]` and then read from the `sys.stdin` file object directly. But having it blindly open `/dev/stdin` seems to work just the same, if we want to keep our list simpler and just hold strings. [1] this just also re-orders the `os.path.isdir()` check as mentioned below, which simplifies things diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index 1aa3d736d80c..311d107bd684 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -515,18 +515,18 @@ def parse_handler(cli_args: argparse.Namespace) -> None: total_test = kunit_parser.Test() total_test.status = kunit_parser.TestStatus.SUCCESS if not parsed_files: - parsed_files.append(input("File path: ")) - - if parsed_files[0] == "debugfs" and len(parsed_files) == 1: + parsed_files.append('/dev/stdin') + elif len(parsed_files) == 1 and parsed_files[0] == "debugfs": parsed_files.pop() for (root, _, files) in os.walk("/sys/kernel/debug/kunit"): parsed_files.extend(os.path.join(root, f) for f in files if f == "results") - - if not parsed_files: - print("No files found.") + if not parsed_files: + print("No files found.") for file in parsed_files: - if os.path.isfile(file): + if os.path.isdir(file): + print("Ignoring directory ", file) + elif os.path.exists(file): print(file) with open(file, 'r', errors='backslashreplace') as f: kunit_output = f.read().splitlines() @@ -536,8 +536,6 @@ def parse_handler(cli_args: argparse.Namespace) -> None: json=cli_args.json) _, test = parse_tests(request, metadata, kunit_output) total_test.subtests.append(test) - elif os.path.isdir(file): - print("Ignoring directory ", file) else: print("Could not find ", file) > - Add more specific warning messages > > tools/testing/kunit/kunit.py | 56 +++++++++++++++++++++++++----------- > 1 file changed, 40 insertions(+), 16 deletions(-) > > diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py > index bc74088c458a..1aa3d736d80c 100755 > --- a/tools/testing/kunit/kunit.py > +++ b/tools/testing/kunit/kunit.py > @@ -511,19 +511,42 @@ def exec_handler(cli_args: argparse.Namespace) -> None: > > > def parse_handler(cli_args: argparse.Namespace) -> None: > - if cli_args.file is None: > - sys.stdin.reconfigure(errors='backslashreplace') # type: ignore > - kunit_output = sys.stdin # type: Iterable[str] > - else: > - with open(cli_args.file, 'r', errors='backslashreplace') as f: > - kunit_output = f.read().splitlines() > - # We know nothing about how the result was created! > - metadata = kunit_json.Metadata() > - request = KunitParseRequest(raw_output=cli_args.raw_output, > - json=cli_args.json) > - result, _ = parse_tests(request, metadata, kunit_output) > - if result.status != KunitStatus.SUCCESS: > - sys.exit(1) > + parsed_files = cli_args.files # type: List[str] > + total_test = kunit_parser.Test() > + total_test.status = kunit_parser.TestStatus.SUCCESS > + if not parsed_files: > + parsed_files.append(input("File path: ")) > + > + if parsed_files[0] == "debugfs" and len(parsed_files) == 1: > + parsed_files.pop() > + for (root, _, files) in os.walk("/sys/kernel/debug/kunit"): > + parsed_files.extend(os.path.join(root, f) for f in files if f == "results") > + > + if not parsed_files: > + print("No files found.") > + > + for file in parsed_files: > + if os.path.isfile(file): Note: perhaps we should reorder this to if os.path.isdir(file): ... elif os.path.exists(file): ... That way this code will then start handling non-regular, yet readable files, like links, etc. That would also help out if we started passing in the magic "/dev/stdin" (since it's a symlink) > + print(file) > + with open(file, 'r', errors='backslashreplace') as f: > + kunit_output = f.read().splitlines() > + # We know nothing about how the result was created! > + metadata = kunit_json.Metadata() > + request = KunitParseRequest(raw_output=cli_args.raw_output, > + json=cli_args.json) > + _, test = parse_tests(request, metadata, kunit_output) > + total_test.subtests.append(test) > + elif os.path.isdir(file): > + print("Ignoring directory ", file) minor nit: `print()` will automatically put a space between arguments, e.g. > Ignoring directory . is what it'll print if I run `kunit.py parse .` It might be better to use a f-string so put quotes around it, like so print(f'Ignoring directory "{file}"')} and below, print(f'Could not find "{file}"') > + else: > + print("Could not find ", file) > + > + if len(parsed_files) > 1: # if more than one file was parsed output total summary > + print('All files parsed.') > + if not request.raw_output: > + stdout.print_with_timestamp(kunit_parser.DIVIDER) > + kunit_parser.bubble_up_test_results(total_test) > + kunit_parser.print_summary_line(total_test) > > > subcommand_handlers_map = { > @@ -569,9 +592,10 @@ def main(argv: Sequence[str]) -> None: > help='Parses KUnit results from a file, ' > 'and parses formatted results.') > add_parse_opts(parse_parser) > - parse_parser.add_argument('file', > - help='Specifies the file to read results from.', > - type=str, nargs='?', metavar='input_file') > + parse_parser.add_argument('files', > + help='List of file paths to read results from or keyword' > + '"debugfs" to read all results from the debugfs directory.', minor spacing note: there are two ' 's here in the series of tabs, i.e. ^I^I^I^I ^I^I'"debugfs" to read all results from the debugfs directory.',$ (using vim's :list formatting) This was copy-pasted from the lines above and below which look like ^I^I^I^I help='List of file paths to read results from or keyword'$ i.e. they use the 2 spaces to align after the tabs. We can just drop those 2 spaces since they won't visually affect the outcome with a tabwidth of 8 spaces. Sorry again for the delayed reply, Daniel