Zubin pushed to branch wip/junit-diffs at Glasgow Haskell Compiler / GHC

Commits:

5 changed files:

Changes:

  • testsuite/driver/junit.py
    ... ... @@ -26,6 +26,8 @@ def junit(t: TestRun) -> ET.ElementTree:
    26 26
                                          classname = tr.way,
    
    27 27
                                          name = '%s(%s)' % (tr.testname, tr.way))
    
    28 28
                 message = [] # type: List[str]
    
    29
    +            if tr.diff:
    
    30
    +                message += ['diff:', '==========', tr.diff]
    
    29 31
                 if tr.stdout:
    
    30 32
                     message += ['', 'stdout:', '==========', tr.stdout]
    
    31 33
                 if tr.stderr:
    

  • testsuite/driver/testglobals.py
    ... ... @@ -291,20 +291,22 @@ class TestResult:
    291 291
         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'
    
    294
    +    __slots__ = 'directory', 'testname', 'reason', 'way', 'stdout', 'stderr', 'diff'
    
    295 295
         def __init__(self,
    
    296 296
                      directory: str,
    
    297 297
                      testname: TestName,
    
    298 298
                      reason: str,
    
    299 299
                      way: WayName,
    
    300 300
                      stdout: Optional[str]=None,
    
    301
    -                 stderr: Optional[str]=None) -> None:
    
    301
    +                 stderr: Optional[str]=None,
    
    302
    +                 diff: Optional[str]=None) -> None:
    
    302 303
             self.directory = directory
    
    303 304
             self.testname = testname
    
    304 305
             self.reason = reason
    
    305 306
             self.way = way
    
    306 307
             self.stdout = stdout
    
    307 308
             self.stderr = stderr
    
    309
    +        self.diff = diff
    
    308 310
     
    
    309 311
     # A performance metric measured in this test run.
    
    310 312
     PerfMetric = NamedTuple('PerfMetric',
    

  • testsuite/driver/testlib.py
    ... ... @@ -1831,7 +1831,8 @@ async def do_test(name: TestName,
    1831 1831
                 reason = '%s (fragile)' % result.reason if result.reason else 'fragile'
    
    1832 1832
                 t.fragile_failures.append(TestResult(directory, name, reason, way,
    
    1833 1833
                                                      stdout=result.stdout,
    
    1834
    -                                                 stderr=result.stderr))
    
    1834
    +                                                 stderr=result.stderr,
    
    1835
    +                                                 diff=result.diff))
    
    1835 1836
         elif result.passed:
    
    1836 1837
             if _expect_pass(way):
    
    1837 1838
                 t.expected_passes.append(TestResult(directory, name, "", way))
    
    ... ... @@ -1850,7 +1851,8 @@ async def do_test(name: TestName,
    1850 1851
                     if_verbose(1, '*** unexpected failure for %s' % full_name)
    
    1851 1852
                     tr = TestResult(directory, name, reason, way,
    
    1852 1853
                                     stdout=result.stdout,
    
    1853
    -                                stderr=result.stderr)
    
    1854
    +                                stderr=result.stderr,
    
    1855
    +                                diff=result.diff)
    
    1854 1856
                     t.unexpected_failures.append(tr)
    
    1855 1857
             else:
    
    1856 1858
                 t.n_expected_failures += 1
    
    ... ... @@ -2022,19 +2024,16 @@ async def do_compile(name: TestName,
    2022 2024
     
    
    2023 2025
         expected_stderr_file = find_expected_file(name, 'stderr', way)
    
    2024 2026
         actual_stderr_file = add_suffix(name, 'comp.stderr')
    
    2025
    -    diff_file_name = in_testdir(add_suffix(name, 'comp.diff'))
    
    2026
    -
    
    2027
    -    if compare_stderr and not await compare_outputs(way, 'stderr',
    
    2027
    +    if compare_stderr:
    
    2028
    +        stderr_match = await compare_outputs(way, 'stderr',
    
    2028 2029
                                join_normalisers(getTestOpts().extra_errmsg_normaliser,
    
    2029 2030
                                                 normalise_errmsg),
    
    2030 2031
                                expected_stderr_file, actual_stderr_file,
    
    2031
    -                           diff_file=diff_file_name,
    
    2032 2032
                                whitespace_normaliser=getattr(getTestOpts(),
    
    2033 2033
                                                              "whitespace_normaliser",
    
    2034
    -                                                         normalise_whitespace)):
    
    2035
    -        stderr = diff_file_name.read_text()
    
    2036
    -        diff_file_name.unlink()
    
    2037
    -        return failBecause('stderr mismatch', stderr=stderr)
    
    2034
    +                                                         normalise_whitespace))
    
    2035
    +        if not stderr_match:
    
    2036
    +            return failBecause('stderr mismatch', diff=stderr_match.diff)
    
    2038 2037
     
    
    2039 2038
         opts = getTestOpts()
    
    2040 2039
         if isGenericStatsTest():
    
    ... ... @@ -2064,10 +2063,11 @@ async def compile_cmp_asm(name: TestName,
    2064 2063
         expected_asm_file = find_expected_file(name, 'asm', way)
    
    2065 2064
         actual_asm_file = add_suffix(name, 's')
    
    2066 2065
     
    
    2067
    -    if not await compare_outputs(way, 'asm',
    
    2066
    +    asm_match = await compare_outputs(way, 'asm',
    
    2068 2067
                                join_normalisers(normalise_errmsg, normalise_asm),
    
    2069
    -                           expected_asm_file, actual_asm_file):
    
    2070
    -        return failBecause('asm mismatch')
    
    2068
    +                           expected_asm_file, actual_asm_file)
    
    2069
    +    if not asm_match:
    
    2070
    +        return failBecause('asm mismatch', diff=asm_match.diff)
    
    2071 2071
     
    
    2072 2072
         # no problems found, this test passed
    
    2073 2073
         return passed()
    
    ... ... @@ -2148,19 +2148,15 @@ async def compile_and_run__(name: TestName,
    2148 2148
             if compile_stderr:
    
    2149 2149
                 expected_stderr_file = find_expected_file(name, 'ghc.stderr', way)
    
    2150 2150
                 actual_stderr_file = add_suffix(name, 'comp.stderr')
    
    2151
    -            diff_file_name = in_testdir(add_suffix(name, 'comp.diff'))
    
    2152
    -
    
    2153
    -            if not await compare_outputs(way, 'stderr',
    
    2151
    +            ghc_stderr_match = await compare_outputs(way, 'stderr',
    
    2154 2152
                                join_normalisers(getTestOpts().extra_errmsg_normaliser,
    
    2155 2153
                                                 normalise_errmsg),
    
    2156 2154
                                expected_stderr_file, actual_stderr_file,
    
    2157
    -                           diff_file=diff_file_name,
    
    2158 2155
                                whitespace_normaliser=getattr(getTestOpts(),
    
    2159 2156
                                                              "whitespace_normaliser",
    
    2160
    -                                                         normalise_whitespace)):
    
    2161
    -             stderr = diff_file_name.read_text()
    
    2162
    -             diff_file_name.unlink()
    
    2163
    -             return failBecause('ghc.stderr mismatch', stderr=stderr)
    
    2157
    +                                                         normalise_whitespace))
    
    2158
    +            if not ghc_stderr_match:
    
    2159
    +                return failBecause('ghc.stderr mismatch', diff=ghc_stderr_match.diff)
    
    2164 2160
     
    
    2165 2161
             opts = getTestOpts()
    
    2166 2162
             extension = exe_extension() if not opts.ignore_extension else ""
    
    ... ... @@ -2449,14 +2445,18 @@ async def simple_run(name: TestName, way: WayName, prog: str, extra_run_opts: st
    2449 2445
             message = format_bad_exit_code_message(exit_code)
    
    2450 2446
             return failBecause(message)
    
    2451 2447
     
    
    2452
    -    if not (opts.ignore_stderr or await stderr_ok(name, way) or opts.combined_output):
    
    2448
    +    stderr_match = CompareOutput(True) if (opts.ignore_stderr or opts.combined_output) else await stderr_ok(name, way)
    
    2449
    +    if not stderr_match:
    
    2453 2450
             return failBecause('bad stderr',
    
    2454 2451
                                stderr=read_stderr(name),
    
    2455
    -                           stdout=read_stdout(name))
    
    2456
    -    if not (opts.ignore_stdout or await stdout_ok(name, way)):
    
    2452
    +                           stdout=read_stdout(name),
    
    2453
    +                           diff=stderr_match.diff)
    
    2454
    +    stdout_match = CompareOutput(True) if opts.ignore_stdout else await stdout_ok(name, way)
    
    2455
    +    if not stdout_match:
    
    2457 2456
             return failBecause('bad stdout',
    
    2458 2457
                                stderr=read_stderr(name),
    
    2459
    -                           stdout=read_stdout(name))
    
    2458
    +                           stdout=read_stdout(name),
    
    2459
    +                           diff=stdout_match.diff)
    
    2460 2460
     
    
    2461 2461
         check_hp = '-hT' in my_rts_flags and opts.check_hp
    
    2462 2462
         check_prof = '-p' in my_rts_flags
    
    ... ... @@ -2464,8 +2464,10 @@ async def simple_run(name: TestName, way: WayName, prog: str, extra_run_opts: st
    2464 2464
         # exit_code > 127 probably indicates a crash, so don't try to run hp2ps.
    
    2465 2465
         if check_hp and (exit_code <= 127 or exit_code == 251) and not await check_hp_ok(name):
    
    2466 2466
             return failBecause('bad heap profile')
    
    2467
    -    if check_prof and not await check_prof_ok(name, way):
    
    2468
    -        return failBecause('bad profile')
    
    2467
    +    if check_prof:
    
    2468
    +        prof_match = await check_prof_ok(name, way)
    
    2469
    +        if not prof_match:
    
    2470
    +            return failBecause('bad profile', diff=prof_match.diff)
    
    2469 2471
     
    
    2470 2472
         # Check the results of stats tests
    
    2471 2473
         if isGenericStatsTest():
    
    ... ... @@ -2559,20 +2561,23 @@ async def interpreter_run(name: TestName,
    2559 2561
     
    
    2560 2562
         # ToDo: if the sub-shell was killed by ^C, then exit
    
    2561 2563
     
    
    2562
    -    if not (opts.ignore_stderr or await stderr_ok(name, way)):
    
    2564
    +    stderr_match = CompareOutput(True) if opts.ignore_stderr else await stderr_ok(name, way)
    
    2565
    +    if not stderr_match:
    
    2563 2566
             if _expect_pass(way):
    
    2564 2567
                 dump_stderr_for('comp', name)
    
    2565 2568
             return failBecause('bad stderr',
    
    2566 2569
                                stderr=read_stderr(name),
    
    2567
    -                           stdout=read_stdout(name))
    
    2568
    -    elif not (opts.ignore_stdout or await stdout_ok(name, way)):
    
    2570
    +                           stdout=read_stdout(name),
    
    2571
    +                           diff=stderr_match.diff)
    
    2572
    +    stdout_match = CompareOutput(True) if opts.ignore_stdout else await stdout_ok(name, way)
    
    2573
    +    if not stdout_match:
    
    2569 2574
             if _expect_pass(way):
    
    2570 2575
                 dump_stderr_for('comp', name)
    
    2571 2576
             return failBecause('bad stdout',
    
    2572 2577
                                stderr=read_stderr(name),
    
    2573
    -                           stdout=read_stdout(name))
    
    2574
    -    else:
    
    2575
    -        return passed()
    
    2578
    +                           stdout=read_stdout(name),
    
    2579
    +                           diff=stdout_match.diff)
    
    2580
    +    return passed()
    
    2576 2581
     
    
    2577 2582
     def split_file(in_fn: Path, delimiter: str, out1_fn: Path, out2_fn: Path):
    
    2578 2583
         # See Note [Universal newlines].
    
    ... ... @@ -2603,7 +2608,15 @@ def get_compiler_flags() -> List[str]:
    2603 2608
     
    
    2604 2609
         return flags
    
    2605 2610
     
    
    2606
    -async def stdout_ok(name: TestName, way: WayName) -> bool:
    
    2611
    +class CompareOutput:
    
    2612
    +    __slots__ = 'ok', 'diff'
    
    2613
    +    def __init__(self, ok: bool, diff: Optional[str]=None) -> None:
    
    2614
    +        self.ok = ok
    
    2615
    +        self.diff = diff
    
    2616
    +    def __bool__(self) -> bool:
    
    2617
    +        return self.ok
    
    2618
    +
    
    2619
    +async def stdout_ok(name: TestName, way: WayName) -> CompareOutput:
    
    2607 2620
        actual_stdout_file = add_suffix(name, 'run.stdout')
    
    2608 2621
        expected_stdout_file = find_expected_file(name, 'stdout', way)
    
    2609 2622
     
    
    ... ... @@ -2612,7 +2625,7 @@ async def stdout_ok(name: TestName, way: WayName) -> bool:
    2612 2625
        check_stdout = getTestOpts().check_stdout
    
    2613 2626
        if check_stdout is not None:
    
    2614 2627
           actual_stdout_path = in_testdir(actual_stdout_file)
    
    2615
    -      return check_stdout(actual_stdout_path, extra_norm)
    
    2628
    +      return CompareOutput(check_stdout(actual_stdout_path, extra_norm))
    
    2616 2629
     
    
    2617 2630
        return await compare_outputs(way, 'stdout', extra_norm,
    
    2618 2631
                               expected_stdout_file, actual_stdout_file)
    
    ... ... @@ -2624,13 +2637,21 @@ def read_stdout( name: TestName ) -> str:
    2624 2637
         else:
    
    2625 2638
             return ''
    
    2626 2639
     
    
    2640
    +def read_diff( diff_file: Path ) -> Optional[str]:
    
    2641
    +    if diff_file.exists():
    
    2642
    +        diff = diff_file.read_text()
    
    2643
    +        diff_file.unlink()
    
    2644
    +        return diff or None
    
    2645
    +    else:
    
    2646
    +        return None
    
    2647
    +
    
    2627 2648
     def dump_stdout( name: TestName ) -> None:
    
    2628 2649
         s = read_stdout(name).strip()
    
    2629 2650
         if s:
    
    2630 2651
             print("Stdout (", name, "):")
    
    2631 2652
             safe_print(s)
    
    2632 2653
     
    
    2633
    -async def stderr_ok(name: TestName, way: WayName) -> bool:
    
    2654
    +async def stderr_ok(name: TestName, way: WayName) -> CompareOutput:
    
    2634 2655
        actual_stderr_file = add_suffix(name, 'run.stderr')
    
    2635 2656
        expected_stderr_file = find_expected_file(name, 'stderr', way)
    
    2636 2657
     
    
    ... ... @@ -2736,48 +2757,48 @@ async def check_hp_ok(name: TestName) -> bool:
    2736 2757
             print("hp2ps error when processing heap profile for " + name)
    
    2737 2758
             return False
    
    2738 2759
     
    
    2739
    -async def check_prof_ok(name: TestName, way: WayName) -> bool:
    
    2760
    +async def check_prof_ok(name: TestName, way: WayName) -> CompareOutput:
    
    2740 2761
         expected_prof_file = find_expected_file(name, 'prof.sample', way)
    
    2741 2762
         expected_prof_path = in_testdir(expected_prof_file)
    
    2742 2763
     
    
    2743 2764
         # Check actual prof file only if we have an expected prof file to
    
    2744 2765
         # compare it with.
    
    2745 2766
         if not expected_prof_path.exists():
    
    2746
    -        return True
    
    2767
    +        return CompareOutput(True)
    
    2747 2768
     
    
    2748 2769
         actual_prof_file = add_suffix(name, 'prof')
    
    2749 2770
         actual_prof_path = in_testdir(actual_prof_file)
    
    2750 2771
     
    
    2751 2772
         if not actual_prof_path.exists():
    
    2752 2773
             print("%s does not exist" % actual_prof_path)
    
    2753
    -        return(False)
    
    2774
    +        return CompareOutput(False)
    
    2754 2775
     
    
    2755 2776
         if actual_prof_path.stat().st_size == 0:
    
    2756 2777
             print("%s is empty" % actual_prof_path)
    
    2757
    -        return(False)
    
    2778
    +        return CompareOutput(False)
    
    2758 2779
     
    
    2759 2780
         return await compare_outputs(way, 'prof', normalise_prof,
    
    2760 2781
                                 expected_prof_file, actual_prof_file,
    
    2761 2782
                                 whitespace_normaliser=normalise_whitespace)
    
    2762 2783
     
    
    2763 2784
     # Compare expected output to actual output, and optionally accept the
    
    2764
    -# new output. Returns true if output matched or was accepted, false
    
    2765
    -# otherwise. See Note [Output comparison] for the meaning of the
    
    2766
    -# normaliser and whitespace_normaliser parameters.
    
    2785
    +# new output. Returns a truthy CompareOutput if output matched or was
    
    2786
    +# accepted, a falsy one (with the diff) otherwise. See Note [Output
    
    2787
    +# comparison] for the meaning of the normaliser and whitespace_normaliser
    
    2788
    +# parameters.
    
    2767 2789
     async def compare_outputs(
    
    2768 2790
             way: WayName,
    
    2769 2791
             kind: str,
    
    2770 2792
             normaliser: OutputNormalizer,
    
    2771 2793
             expected_file: Path,
    
    2772 2794
             actual_file: Path,
    
    2773
    -        diff_file: Optional[Path]=None,
    
    2774
    -        whitespace_normaliser: OutputNormalizer=lambda x:x) -> bool:
    
    2795
    +        whitespace_normaliser: OutputNormalizer=lambda x:x) -> CompareOutput:
    
    2775 2796
     
    
    2776 2797
         # Respect ignore_stdout and ignore_stderr options
    
    2777 2798
         if kind == 'stderr' and getTestOpts().ignore_stderr:
    
    2778
    -        return True
    
    2799
    +        return CompareOutput(True)
    
    2779 2800
         if kind == 'stdout' and getTestOpts().ignore_stdout:
    
    2780
    -        return True
    
    2801
    +        return CompareOutput(True)
    
    2781 2802
     
    
    2782 2803
         expected_path = in_srcdir(expected_file)
    
    2783 2804
         actual_path = in_testdir(actual_file)
    
    ... ... @@ -2797,7 +2818,7 @@ async def compare_outputs(
    2797 2818
     
    
    2798 2819
         # See Note [Output comparison].
    
    2799 2820
         if whitespace_normaliser(expected_str) == whitespace_normaliser(actual_str):
    
    2800
    -        return True
    
    2821
    +        return CompareOutput(True)
    
    2801 2822
         else:
    
    2802 2823
             if config.verbose >= 1 and _expect_pass(way):
    
    2803 2824
                 print('Actual ' + kind + ' output differs from expected:')
    
    ... ... @@ -2809,6 +2830,7 @@ async def compare_outputs(
    2809 2830
             actual_normalised_path = add_suffix(actual_path, 'normalised')
    
    2810 2831
             write_file(actual_normalised_path, actual_str)
    
    2811 2832
     
    
    2833
    +        diff_file = add_suffix(actual_path, 'diff')
    
    2812 2834
             if config.verbose >= 1 and _expect_pass(way):
    
    2813 2835
                 # See Note [Output comparison].
    
    2814 2836
                 r = await runCmd('diff -uw "{0}" "{1}"'.format(null2unix_null(expected_normalised_path),
    
    ... ... @@ -2823,13 +2845,14 @@ async def compare_outputs(
    2823 2845
                                                                actual_normalised_path),
    
    2824 2846
                                stdout=diff_file,
    
    2825 2847
                                print_output=True)
    
    2826
    -        elif diff_file: diff_file.open('ab').close() # Make sure the file exists still as
    
    2827
    -                                                     # we will try to read it later
    
    2848
    +        else:
    
    2849
    +            diff_file.open('ab').close() # Make sure the file exists still as
    
    2850
    +                                         # we will try to read it later
    
    2828 2851
     
    
    2829 2852
             if config.accept and (getTestOpts().expect == 'fail' or
    
    2830 2853
                                   way in getTestOpts().expect_fail_for):
    
    2831 2854
                 if_verbose(1, 'Test is expected to fail. Not accepting new output.')
    
    2832
    -            return False
    
    2855
    +            return CompareOutput(False, read_diff(diff_file))
    
    2833 2856
             elif config.accept and actual_raw:
    
    2834 2857
                 if config.accept_platform:
    
    2835 2858
                     if_verbose(1, 'Accepting new output for platform "'
    
    ... ... @@ -2843,11 +2866,11 @@ async def compare_outputs(
    2843 2866
                     if_verbose(1, 'Accepting new output.')
    
    2844 2867
     
    
    2845 2868
                 write_file(expected_path, actual_raw)
    
    2846
    -            return True
    
    2869
    +            return CompareOutput(True)
    
    2847 2870
             elif config.accept:
    
    2848 2871
                 if_verbose(1, 'No output. Deleting "{0}".'.format(expected_path))
    
    2849 2872
                 expected_path.unlink()
    
    2850
    -            return True
    
    2873
    +            return CompareOutput(True)
    
    2851 2874
             else:
    
    2852 2875
                 if config.unexpected_output_dir is not None:
    
    2853 2876
                     ghc_root = expected_path.relative_to(config.top.parent)
    
    ... ... @@ -2855,7 +2878,7 @@ async def compare_outputs(
    2855 2878
                     out.parent.mkdir(exist_ok=True, parents=True)
    
    2856 2879
                     write_file(out, actual_raw)
    
    2857 2880
     
    
    2858
    -            return False
    
    2881
    +            return CompareOutput(False, read_diff(diff_file))
    
    2859 2882
     
    
    2860 2883
     # Checks that each line from pattern_file is present in actual_file as
    
    2861 2884
     # a substring or regex pattern depending on is_substring.
    

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

  • testsuite/tests/codeGen/should_run/cgrun001.stdout
    1
    --42
    1
    +-43