Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC

Commits:

5 changed files:

Changes:

  • testsuite/driver/junit.py
    ... ... @@ -26,10 +26,14 @@ 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:
    
    32 34
                     message += ['', 'stderr:', '==========', tr.stderr]
    
    35
    +            if not message:
    
    36
    +                message = [tr.reason]
    
    33 37
     
    
    34 38
                 result = ET.SubElement(testcase, kind,
    
    35 39
                                        type = res_type,
    

  • testsuite/driver/perf_notes.py
    ... ... @@ -653,7 +653,11 @@ def check_stats_change(actual: PerfStat,
    653 653
             error = str(change) + ' from ' + baseline.perfStat.test_env + \
    
    654 654
                     ' baseline @ %s' % baseline.commit
    
    655 655
             print(actual.metric, error + ':')
    
    656
    -        result = failBecause('stat ' + error, tag='stat')
    
    656
    +        dev = 100.0 if expected_val == 0 else round(((float(actual.value) * 100) / int(expected_val)) - 100, 1)
    
    657
    +        change_line = (f'{actual.metric} {change.value} from {baseline.perfStat.test_env} '
    
    658
    +                       f'baseline @ {baseline.commit[:7]}: {expected_val} -> {actual.value} '
    
    659
    +                       f'({dev:+g}%, allowed {acceptance_window.describe()})')
    
    660
    +        result = failBecause('stat ' + change_line, tag='stat')
    
    657 661
     
    
    658 662
         if not change_allowed or force_print:
    
    659 663
             length = max(len(str(x)) for x in [expected_val, lowerBound, upperBound, actual.value])
    

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