Simon Jakobi pushed to branch wip/sjakobi/T18922-filecheck-poc at Glasgow Haskell Compiler / GHC

Commits:

3 changed files:

Changes:

  • testsuite/driver/testglobals.py
    ... ... @@ -291,7 +291,7 @@ class TestResult:
    291 291
         expected_failures, framework_failures, framework_warnings, unexpected_passes,
    
    292 292
         unexpected_failures, unexpected_stat_failures lists of TestRun.
    
    293 293
         """
    
    294
    -    __slots__ = 'directory', 'testname', 'reason', 'way', 'stdout', 'stderr', 'diff', 'runtime'
    
    294
    +    __slots__ = 'directory', 'testname', 'reason', 'way', 'stdout', 'stderr', 'diff', 'diff_label', 'runtime'
    
    295 295
         def __init__(self,
    
    296 296
                      directory: str,
    
    297 297
                      testname: TestName,
    
    ... ... @@ -300,6 +300,7 @@ class TestResult:
    300 300
                      stdout: Optional[str]=None,
    
    301 301
                      stderr: Optional[str]=None,
    
    302 302
                      diff: Optional[str]=None,
    
    303
    +                 diff_label: Optional[str]=None,
    
    303 304
                      runtime: Optional[float]=None) -> None:
    
    304 305
             self.directory = directory
    
    305 306
             self.testname = testname
    
    ... ... @@ -308,6 +309,8 @@ class TestResult:
    308 309
             self.stdout = stdout
    
    309 310
             self.stderr = stderr
    
    310 311
             self.diff = diff
    
    312
    +        # Heading for diff in reports, None for the default one
    
    313
    +        self.diff_label = diff_label
    
    311 314
             self.runtime = runtime # Walltime
    
    312 315
     
    
    313 316
     # A performance metric measured in this test run.
    

  • testsuite/driver/testlib.py
    ... ... @@ -1861,6 +1861,7 @@ async def do_test(name: TestName,
    1861 1861
                                                      stdout=result.stdout,
    
    1862 1862
                                                      stderr=result.stderr,
    
    1863 1863
                                                      diff=result.diff,
    
    1864
    +                                                 diff_label=result.diff_label,
    
    1864 1865
                                                      runtime=runtime))
    
    1865 1866
         elif result.passed:
    
    1866 1867
             if _expect_pass(way):
    
    ... ... @@ -1884,6 +1885,7 @@ async def do_test(name: TestName,
    1884 1885
                                     stdout=result.stdout,
    
    1885 1886
                                     stderr=result.stderr,
    
    1886 1887
                                     diff=result.diff,
    
    1888
    +                                diff_label=result.diff_label,
    
    1887 1889
                                     runtime=runtime)
    
    1888 1890
                     t.unexpected_failures.append(tr)
    
    1889 1891
             else:
    
    ... ... @@ -2070,7 +2072,8 @@ async def do_compile(name: TestName,
    2070 2072
                                                              "whitespace_normaliser",
    
    2071 2073
                                                              normalise_whitespace))
    
    2072 2074
             if not stderr_match:
    
    2073
    -            return failBecause('stderr mismatch', diff=stderr_match.diff)
    
    2075
    +            return failBecause('stderr mismatch', diff=stderr_match.diff,
    
    2076
    +                               diff_label=stderr_match.diff_label)
    
    2074 2077
     
    
    2075 2078
         opts = getTestOpts()
    
    2076 2079
         if isGenericStatsTest():
    
    ... ... @@ -2491,13 +2494,15 @@ async def simple_run(name: TestName, way: WayName, prog: str, extra_run_opts: st
    2491 2494
             return failBecause('bad stderr',
    
    2492 2495
                                stderr=None if stderr_match.diff else read_stderr(name),
    
    2493 2496
                                stdout=read_stdout(name),
    
    2494
    -                           diff=stderr_match.diff)
    
    2497
    +                           diff=stderr_match.diff,
    
    2498
    +                           diff_label=stderr_match.diff_label)
    
    2495 2499
         stdout_match = CompareOutput(True) if opts.ignore_stdout else await stdout_ok(name, way)
    
    2496 2500
         if not stdout_match:
    
    2497 2501
             return failBecause('bad stdout',
    
    2498 2502
                                stderr=read_stderr(name),
    
    2499 2503
                                stdout=None if stdout_match.diff else read_stdout(name),
    
    2500
    -                           diff=stdout_match.diff)
    
    2504
    +                           diff=stdout_match.diff,
    
    2505
    +                           diff_label=stdout_match.diff_label)
    
    2501 2506
     
    
    2502 2507
         check_hp = '-hT' in my_rts_flags and opts.check_hp
    
    2503 2508
         check_prof = '-p' in my_rts_flags
    
    ... ... @@ -2610,7 +2615,8 @@ async def interpreter_run(name: TestName,
    2610 2615
             return failBecause('bad stderr',
    
    2611 2616
                                stderr=None if stderr_match.diff else read_stderr(name),
    
    2612 2617
                                stdout=read_stdout(name),
    
    2613
    -                           diff=stderr_match.diff)
    
    2618
    +                           diff=stderr_match.diff,
    
    2619
    +                           diff_label=stderr_match.diff_label)
    
    2614 2620
         stdout_match = CompareOutput(True) if opts.ignore_stdout else await stdout_ok(name, way)
    
    2615 2621
         if not stdout_match:
    
    2616 2622
             if _expect_pass(way):
    
    ... ... @@ -2618,7 +2624,8 @@ async def interpreter_run(name: TestName,
    2618 2624
             return failBecause('bad stdout',
    
    2619 2625
                                stderr=read_stderr(name),
    
    2620 2626
                                stdout=None if stdout_match.diff else read_stdout(name),
    
    2621
    -                           diff=stdout_match.diff)
    
    2627
    +                           diff=stdout_match.diff,
    
    2628
    +                           diff_label=stdout_match.diff_label)
    
    2622 2629
         return passed()
    
    2623 2630
     
    
    2624 2631
     def split_file(in_fn: Path, delimiter: str, out1_fn: Path, out2_fn: Path):
    
    ... ... @@ -2651,10 +2658,12 @@ def get_compiler_flags() -> List[str]:
    2651 2658
         return flags
    
    2652 2659
     
    
    2653 2660
     class CompareOutput:
    
    2654
    -    __slots__ = 'ok', 'diff'
    
    2655
    -    def __init__(self, ok: bool, diff: Optional[str]=None) -> None:
    
    2661
    +    __slots__ = 'ok', 'diff', 'diff_label'
    
    2662
    +    def __init__(self, ok: bool, diff: Optional[str]=None,
    
    2663
    +                 diff_label: Optional[str]=None) -> None:
    
    2656 2664
             self.ok = ok
    
    2657 2665
             self.diff = diff
    
    2666
    +        self.diff_label = diff_label
    
    2658 2667
         def __bool__(self) -> bool:
    
    2659 2668
             return self.ok
    
    2660 2669
     
    
    ... ... @@ -2945,16 +2954,41 @@ def run_filecheck(way: WayName,
    2945 2954
         # CQ[filecheck-platform-prefixes]
    
    2946 2955
         # Q: WS32/WS64 are always enabled, mirroring the -ws-NN golden suffixes;
    
    2947 2956
         #    should OS/arch prefixes follow (cf. find_expected_file's platform chain)?
    
    2948
    -    opts = Options(match_filename=str(in_srcdir(check_file)),
    
    2957
    +    prefixes = prefixes + ['WS' + config.wordsize]
    
    2958
    +    opts = Options(match_filename=os.path.relpath(in_srcdir(check_file)),
    
    2949 2959
                        input_file=str(actual_path),
    
    2950
    -                   check_prefixes=prefixes + ['WS' + config.wordsize])
    
    2960
    +                   check_prefixes=prefixes)
    
    2951 2961
         matcher = Matcher.from_opts(opts)
    
    2952 2962
         matcher.stderr = io.StringIO()
    
    2953
    -    ok = matcher.run() == 0
    
    2954
    -    report = matcher.stderr.getvalue()
    
    2955
    -    if not ok and config.verbose >= 1 and _expect_pass(way):
    
    2963
    +    if matcher.run() == 0:
    
    2964
    +        return CompareOutput(True)
    
    2965
    +    report = matcher.stderr.getvalue() + filecheck_input_window(matcher, prefixes)
    
    2966
    +    if config.verbose >= 1 and _expect_pass(way):
    
    2956 2967
             print(report)
    
    2957
    -    return CompareOutput(ok, None if ok else report)
    
    2968
    +    return CompareOutput(False, report, 'FileCheck report:')
    
    2969
    +
    
    2970
    +# The input from where the failing check started looking, numbered like the
    
    2971
    +# tool's positions; a substitute for LLVM FileCheck's annotated input dump.
    
    2972
    +def filecheck_input_window(matcher, prefixes: List[str], max_lines: int=40) -> str:
    
    2973
    +    content = matcher.file.content
    
    2974
    +    lines = content.split('\n')
    
    2975
    +    if content.endswith('\n'):
    
    2976
    +        lines.pop()
    
    2977
    +    # CQ[filecheck-window-anchor]
    
    2978
    +    # Q: a failing CHECK-NOT raises from inside the deferred check, leaving
    
    2979
    +    #    negative_matches_start set; is relying on that too fragile?
    
    2980
    +    start = matcher.ctx.negative_matches_start
    
    2981
    +    if start is None:
    
    2982
    +        start = matcher.file.range.start
    
    2983
    +    first = content.count('\n', 0, start)
    
    2984
    +    shown = lines[first:first + max_lines]
    
    2985
    +    out = ['Input from line {} of {}, checked with prefixes {}:'.format(
    
    2986
    +               first + 1, len(lines), ', '.join(prefixes))]
    
    2987
    +    out += ['{:4} | {}'.format(first + i + 1, l) for i, l in enumerate(shown)]
    
    2988
    +    omitted = len(lines) - first - len(shown)
    
    2989
    +    if omitted > 0:
    
    2990
    +        out.append('... ({} more lines)'.format(omitted))
    
    2991
    +    return '\n'.join(out) + '\n'
    
    2958 2992
     
    
    2959 2993
     # Checks that each line from pattern_file is present in actual_file as
    
    2960 2994
     # a substring or regex pattern depending on is_substring.
    
    ... ... @@ -3814,7 +3848,8 @@ def printTestOutputSummary(file: TextIO,
    3814 3848
                 result.testname, ', '.join(ways), result.directory + os.sep, result.reason)
    
    3815 3849
             file.write(colored_if(color, Color.RED, header) + '\n')
    
    3816 3850
             # See Note [Redundant output in test results] for why these don't overlap.
    
    3817
    -        for label, contents in [('Output diff (expected vs actual):', diff),
    
    3851
    +        diff_label = result.diff_label or 'Output diff (expected vs actual):'
    
    3852
    +        for label, contents in [(diff_label, diff),
    
    3818 3853
                                     ('Captured stdout:', result.stdout),
    
    3819 3854
                                     ('Captured stderr:', result.stderr)]:
    
    3820 3855
                 if contents and contents.strip():
    

  • testsuite/driver/testutil.py
    ... ... @@ -15,6 +15,7 @@ PassFail = NamedTuple('PassFail',
    15 15
                            ('stderr', Optional[str]),
    
    16 16
                            ('stdout', Optional[str]),
    
    17 17
                            ('diff', Optional[str]),
    
    18
    +                       ('diff_label', Optional[str]),
    
    18 19
                            ('hc_opts', Optional[str]),
    
    19 20
                            ])
    
    20 21
     
    
    ... ... @@ -28,16 +29,19 @@ def passed(hc_opts=None) -> PassFail:
    28 29
                         stderr=None,
    
    29 30
                         stdout=None,
    
    30 31
                         diff=None,
    
    32
    +                    diff_label=None,
    
    31 33
                         hc_opts=hc_opts)
    
    32 34
     
    
    33 35
     def failBecause(reason: str,
    
    34 36
                     tag: Optional[str]=None,
    
    35 37
                     stderr: Optional[str]=None,
    
    36 38
                     stdout: Optional[str]=None,
    
    37
    -                diff: Optional[str]=None
    
    39
    +                diff: Optional[str]=None,
    
    40
    +                diff_label: Optional[str]=None
    
    38 41
                     ) -> PassFail:
    
    39 42
         return PassFail(passed=False, reason=reason, tag=tag,
    
    40
    -                    stderr=stderr, stdout=stdout, diff=diff, hc_opts=None)
    
    43
    +                    stderr=stderr, stdout=stdout, diff=diff,
    
    44
    +                    diff_label=diff_label, hc_opts=None)
    
    41 45
     
    
    42 46
     def strip_quotes(s: str) -> str:
    
    43 47
         # Don't wrap commands to subprocess.call/Popen in quotes.