public inbox for bunsen@sourceware.org
 help / color / mirror / Atom feed
From: "Serhei Makarov" <me@serhei.io>
To: "Keith Seitz" <keiths@redhat.com>, Bunsen <bunsen@sourceware.org>
Subject: Re: [PATCH 1/4] Rewrite gdb.parse_dejagnu_sum
Date: Thu, 19 Aug 2021 08:40:55 -0400	[thread overview]
Message-ID: <3ad2b380-d3f5-438d-bd38-3f16470a159a@www.fastmail.com> (raw)
In-Reply-To: <20210818192639.2362335-2-keiths@redhat.com>

Hello Keith,

I had a look, the 4 patches look good to commit. I will do additional testing with the SystemTap data next week.

All the best,
      Serhei

On Wed, Aug 18, 2021, at 3:26 PM, Keith Seitz via Bunsen wrote:
> This patch rewrites gdb.parse_dejagnu_sum, making it significantly
> simple and more reliable. With the current version of this function,
> I have been consistently seeing 8,000+ "missing" tests -- tests
> that are recorded in gdb.sum but never make it into the Bunsen
> database.
> 
> After chasing down a number of problems, I found it was much easier
> to simply rewrite this function. Consequently, my Bunsen imports of
> gdb.sum now account for every test.
> ---
>  scripts-main/gdb/parse_dejagnu.py | 206 ++++++++++++++++--------------
>  1 file changed, 110 insertions(+), 96 deletions(-)
> 
> diff --git a/scripts-main/gdb/parse_dejagnu.py 
> b/scripts-main/gdb/parse_dejagnu.py
> index b56ed3c..fc6a30e 100755
> --- a/scripts-main/gdb/parse_dejagnu.py
> +++ b/scripts-main/gdb/parse_dejagnu.py
> @@ -71,116 +71,126 @@ def get_outcome_subtest(line):
>      if m is None: return None
>      return m.group('outcome'), m.group('subtest')
>  
> +# Normalize the test named NAME. NAME_DICT is used to track these.
> +#
> +# This is unfortunately quite complex.
> +
> +def normalize_name(name_dict, name):
> +
> +    assert(name is not None)
> +    assert(name != "")
> +
> +    # The buildbot does:
> +    #
> +    #  test_name = re.sub (r'(\s+)? \(.*\)$', r'', orig_name)
> +    #
> +    # But this is overly aggressive, causing thousands of duplicate
> +    # names to be recorded.
> +    #
> +    # Instead, try to remove known constant statuses. Unfortunately, this is
> +    # quite slow, but it is the most reliable way to avoid 10,000 duplicate
> +    # names from invading the database.
> +    test_name = re.sub(r' \((PRMS.*|timeout|eof|GDB internal error'
> +                       r'|the program exited|the program is no longer running'
> +                       r'|got interactive prompt|got breakpoint menu'
> +                       r'|resync count exceeded|bad file format|file not found'
> +                       r'|incomplete note section|unexpected output'
> +                       r'|inferior_not_stopped|stopped at wrong place'
> +                       r'|unknown output after running|dwarf version unhandled'
> +                       r'|line numbers scrambled\?|out of virtual memory'
> +                       r'|missing filename|missing module'
> +                       r'|missing /usr/bin/prelink)\)', r'', name)
> +
> +    if test_name in name_dict:
> +        # If the test is already present in the file list, then
> +        # we include a unique identifier in the end of it, in the
> +        # form or '<<N>>' (where N is a number >= 2).  This is
> +        # useful because the GDB testsuite is full of non-unique
> +        # test messages.
> +        i = 2
> +        while True:
> +            nname = test_name + ' <<' + str (i) + '>>'
> +            if nname not in name_dict:
> +                break
> +            i += 1
> +        test_name = nname
> +
> +    name_dict[test_name] = test_name
> +    return test_name
> +
>  def parse_dejagnu_sum(testrun, sumfile, all_cases=None,
>                        consolidate_pass=False, verbose=True):
> -#                      consolidate_pass=True, verbose=True):
>      if testrun is None: return None
>      f = openfile_or_xz(sumfile)
>  
>      last_exp = None
> -    last_test_passed = False # at least one pass and no fails
> -    last_test_failed = False # at least one fail
> -    failed_subtests = [] # XXX Better known as 'unpassed'?
> -    passed_subtests = []
> -    failed_subtests_summary = 0
> -    passed_subtests_summary = 0
> +    counts = dict()
> +    names = dict()
> +
> +    # The global test_outcome_map doesn't contain all of our
> +    # outcomes. Add those now.
> +    test_outcome_map['PATH'] = 'PATH'  # Tests with paths in their names
> +
> +    # Clear counts dictionary
> +    counts = dict.fromkeys(test_outcome_map, 0)
>  
> +    # Iterate over lines in the sum file.
>      for cur in Cursor(sumfile, path=os.path.basename(sumfile), input_stream=f):
>          line = cur.line
>  
> -        # XXX need to handle several .sum formats
> -        # buildbot format :: all lines are outcome lines, include the 
> .exp
> -        # regular format :: outcome lines separated by "Running 
> <expname>.exp ..."
> -        outcome, expname, subtest = None, None, None
> +        # There's always an exception.  ERRORs are not output the same
> +        # way as other test results.  They simply list a reason.
> +        # FIXME: ERRORs typically span a range of lines
>          info = get_expname_subtest(line)
> -        if info is not None:
> -            outcome, expname, subtest = info
> -        elif (line.startswith("Running") and ".exp ..." in line):
> -            outcome = None
> -            expname = get_running_exp(line)
> -        else:
> -            info = get_outcome_subtest(line)
> -            if info is not None:
> -                outcome, subtest = info
> -
> -        # XXX these situations mark an .exp boundary:
> -        finished_exp = False
> -        if expname != last_exp and expname is not None and last_exp is 
> not None:
> -            finished_exp = True
> -        elif "Summary ===" in line:
> -            finished_exp = True
> -
> -        if finished_exp:
> -            running_cur.line_end = cur.line_end-1
> -            if consolidate_pass and last_test_passed:
> -                testrun.add_testcase(name=last_exp,
> -                                     outcome='PASS',
> -                                     origin_sum=running_cur)
> -            elif last_test_passed:
> -                # Report each passed subtest individually:
> -                for passed_subtest, outcome, cursor in passed_subtests:
> -                    testrun.add_testcase(name=last_exp,
> -                                         outcome=outcome,
> -                                         subtest=passed_subtest,
> -                                         origin_sum=cursor)
> -            # Report all failed and untested subtests:
> -            for failed_subtest, outcome, cursor in failed_subtests:
> -                testrun.add_testcase(name=last_exp,
> -                                     outcome=outcome,
> -                                     subtest=failed_subtest,
> -                                     origin_sum=cursor)
> -
> -        if expname is not None and expname != last_exp:
> -            last_exp = expname
> -            running_cur = Cursor(start=cur)
> -            last_test_passed = False
> -            last_test_failed = False
> -            failed_subtests = []
> -            passed_subtests = []
> +        if info is None:
> +            if line.startswith('ERROR:'):
> +                # In this case, the "subtest" is actually the reason
> +                # for the failure. LAST_EXP is not necessarily strictly
> +                # correct, but we would have to watch for additional
> +                # messages (Running TESTFILE ...) to make this work 
> properly.
> +                # In practice, it's not typically a problem.
> +                info = ('ERROR', last_exp, line[len('ERROR: '):])
> +            elif line.endswith(".exp:\n"):
> +                # An unnamed test. It happens.
> +                line = line[:-1] + " " + "UNNAMED_TEST" + "\n"
> +                info = get_expname_subtest(line)
> +                if info is None:
> +                    # We tried. Nothing else we can do.
> +                    print("WARNING: unknown expname/subtest in outcome 
> line --", line, file=sys.stderr)
> +                    continue
> +            else:
> +                continue
>  
> -        if outcome is None:
> +        outcome, expname, subtest = info
> +
> +        # Warn and skip any outcome that is not in test_outcome_map!
> +        # It will cause an exception later.
> +        if outcome not in test_outcome_map:
> +            print(f'WARNING: unexpected test outcome ({outcome}) in 
> line -- {line}')
>              continue
> -        # XXX The line contains a test outcome.
> -        synth_line = line
> -        if all_cases is not None and expname is None:
> -            # XXX force embed the expname into the line for later 
> annotation code
> -            synth_line = str(outcome) + ": " + last_exp + ": " + 
> str(subtest)
> -        all_cases.append(synth_line)
> -
> -        # TODO: Handle other dejagnu outcomes if they show up:
> -        if line.startswith("FAIL: ") \
> -           or line.startswith("KFAIL: ") \
> -           or line.startswith("XFAIL: ") \
> -           or line.startswith("ERROR: tcl error sourcing"):
> -            last_test_failed = True
> -            last_test_passed = False
> -            failed_subtests.append((line,
> -                                    check_mapping(line, 
> test_outcome_map, start=True),
> -                                    cur)) # XXX single line
> -            failed_subtests_summary += 1
> -        if line.startswith("UNTESTED: ") \
> -           or line.startswith("UNSUPPORTED: ") \
> -           or line.startswith("UNRESOLVED: "):
> -            # don't update last_test_{passed,failed}
> -            failed_subtests.append((line,
> -                                    check_mapping(line, 
> test_outcome_map, start=True),
> -                                    cur))
> -            # don't tally
> -        if line.startswith("PASS: ") \
> -           or line.startswith("XPASS: ") \
> -           or line.startswith("IPASS: "):
> -            if not last_test_failed: # no fails so far
> -                last_test_passed = True
> -            if not consolidate_pass:
> -                passed_subtests.append((line,
> -                                        check_mapping(line, 
> test_outcome_map, start=True),
> -                                        cur))
> -            passed_subtests_summary += 1
> -    f.close()
> +        if last_exp != expname:
> +            last_exp = expname
> +            names.clear()
> +
> +        # Normalize the name to account for duplicates.
> +        subtest = normalize_name(names, subtest)
>  
> -    testrun.pass_count = passed_subtests_summary
> -    testrun.fail_count = failed_subtests_summary
> +        if all_cases is not None:
> +            # ERRORs are not appended to outcome_lines!
> +            if outcome != "ERROR":
> +                all_cases.append(line)
>  
> +        if consolidate_pass:
> +            pass # not implemented
> +        else:
> +            testrun.add_testcase(name=expname, outcome=outcome,
> +                                 subtest=subtest, origin_sum=cur)
> +            counts[outcome] += 1
> +    f.close()
> +
> +    testrun.pass_count = counts['PASS'] + counts['XPASS'] + counts['KPASS']
> +    testrun.fail_count = counts['FAIL'] + counts['XFAIL'] + counts['KFAIL'] \
> +        + counts['ERROR'] # UNTESTED, UNSUPPORTED, UNRESOLVED not tallied
>      return testrun
>  
>  def annotate_dejagnu_log(testrun, logfile, outcome_lines=[],
> @@ -218,7 +228,11 @@ def annotate_dejagnu_log(testrun, logfile, 
> outcome_lines=[],
>      # (1b) Build a map of outcome_lines:
>      testcase_line_start = {} # .exp name -> index of first 
> outcome_line with this name
>      for j in range(len(outcome_lines)):
> -        outcome, expname, subtest = 
> get_expname_subtest(outcome_lines[j])
> +        info = get_expname_subtest(outcome_lines[j])
> +        if info is None:
> +            print("WARNING: unknown expname/subtest in outcome line 
> --", outcome_lines[j], file=sys.stderr)
> +            continue
> +        outcome, expname, subtest = info
>          if expname not in testcase_line_start:
>              testcase_line_start[expname] = j
>  
> -- 
> 2.31.1
> 
> 


-- 
All the best,
    Serhei
    http://serhei.io

  reply	other threads:[~2021-08-19 12:41 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-18 19:26 Fix GDB data imports Keith Seitz
2021-08-18 19:26 ` [PATCH 1/4] Rewrite gdb.parse_dejagnu_sum Keith Seitz
2021-08-19 12:40   ` Serhei Makarov [this message]
2021-08-19 15:33     ` Keith Seitz
2021-08-18 19:26 ` [PATCH 2/4] Add verbose option to +summarize Keith Seitz
2021-08-18 19:26 ` [PATCH 3/4] Add new outcomes to summarize script Keith Seitz
2021-08-18 19:26 ` [PATCH 4/4] Add support for reading the target_board Keith Seitz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3ad2b380-d3f5-438d-bd38-3f16470a159a@www.fastmail.com \
    --to=me@serhei.io \
    --cc=bunsen@sourceware.org \
    --cc=keiths@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).