David Eichmann pushed to branch wip/davide/io_manager_tests at Glasgow Haskell Compiler / GHC

Commits:

9 changed files:

Changes:

  • rts/RtsUtils.c
    ... ... @@ -397,6 +397,7 @@ void printRtsInfo(const RtsConfig rts_config) {
    397 397
             rts_config.rts_opts != NULL ? rts_config.rts_opts : "");
    
    398 398
         selectIOManager(); /* resolve the io-manager, accounting for flags  */
    
    399 399
         mkRtsInfoPair("I/O manager default",     showIOManager());
    
    400
    +    mkRtsInfoPair("I/O managers",            &IOMGRS_ENABLED_STR[1]); // skips the initial ' ' character in IOMGRS_ENABLED_STR
    
    400 401
         printf(" ]\n");
    
    401 402
     }
    
    402 403
     
    

  • testsuite/config/ghc
    ... ... @@ -257,7 +257,9 @@ def get_compiler_info():
    257 257
                         )
    
    258 258
     
    
    259 259
                 except Exception as err:
    
    260
    -                print("Exception thrown in testsuite/config/ghc.get_compiler_info: %s", err)
    
    260
    +                # We don't throw so that we can still discover tests before we've built
    
    261
    +                # GHC (i.e. config.compiler doesn't yet exist).
    
    262
    +                print("Exception thrown in testsuite/config/ghc.get_compiler_info: ", err)
    
    261 263
     
    
    262 264
                 else:
    
    263 265
                     res = p.returncode == 0
    
    ... ... @@ -315,6 +317,58 @@ def get_compiler_info():
    315 317
             config.ghc_th_way       = "normal"
    
    316 318
             config.ghc_th_hi_ext    = "hi"
    
    317 319
     
    
    320
    +    # I/O manager discovery.
    
    321
    +    #
    
    322
    +    # Which I/O managers (GHC's `--io-manager` RTS flag) a given RTS build
    
    323
    +    # supports depends on the target platform and on whether the RTS is the
    
    324
    +    # threaded or non-threaded flavour (see rts/IOManager.h's
    
    325
    +    # IOMGR_ENABLED_* macros). Rather than duplicate that platform/way
    
    326
    +    # dependent logic here, we ask the RTS itself using `+RTS --info -RTS`
    
    327
    +    # and extract the "I/O managers" value from stdout.
    
    328
    +    def discover_io_managers_generic(extra_flags):
    
    329
    +        import tempfile
    
    330
    +
    
    331
    +        test_src = 'main :: IO ()\nmain = return ()\n'
    
    332
    +
    
    333
    +        res = [None]
    
    334
    +
    
    335
    +        with tempfile.TemporaryDirectory() as d:
    
    336
    +            src = Path(d) / 'IOMgrProbe.hs'
    
    337
    +            src.write_text(test_src)
    
    338
    +            exe = Path(d) / ('IoMgrProbe' + config.exeext)
    
    339
    +
    
    340
    +            # -rtsopts is needed so that the resulting binary will even
    
    341
    +            # consider an --io-manager= flag (rather than immediately
    
    342
    +            # rejecting all "unsafe" RTS options).
    
    343
    +            cmd = '{} -v0 -rtsopts -o {} {} {}'.format(
    
    344
    +                                    config.compiler,
    
    345
    +                                    exe,
    
    346
    +                                    src,
    
    347
    +                                    ' '.join(extra_flags))
    
    348
    +            try:
    
    349
    +                cp = subprocess.run(
    
    350
    +                    cmd,
    
    351
    +                    shell=True,
    
    352
    +                    cwd=d,
    
    353
    +                    stdout=subprocess.DEVNULL,
    
    354
    +                    stderr=None if config.verbose >= 3 else subprocess.DEVNULL)
    
    355
    +                if cp.returncode != 0 or not exe.exists():
    
    356
    +                    raise Exception("Error in testsuite/config/ghc.discover_io_managers: failed to compile with: {}".format(cmd))
    
    357
    +                res = list(discover_io_managers(exe, d))
    
    358
    +
    
    359
    +            except Exception as err:
    
    360
    +                # We don't throw so that we can still discover tests before we've built
    
    361
    +                # GHC (i.e. config.compiler doesn't yet exist).
    
    362
    +                print("Exception thrown in testsuite/config/ghc.discover_io_managers_generic: ", err)
    
    363
    +
    
    364
    +        return res
    
    365
    +
    
    366
    +    config.io_managers_nonthreaded = discover_io_managers_generic([])
    
    367
    +    config.io_managers_threaded = (
    
    368
    +        discover_io_managers_generic(['-threaded']) if config.ghc_with_threaded_rts
    
    369
    +        else [])
    
    370
    +    config.io_managers_all = list(set(config.io_managers_nonthreaded).union(set(config.io_managers_threaded)))
    
    371
    +
    
    318 372
         config.cmm_cpp_is_gcc = test_compile(
    
    319 373
             ['-c'],
    
    320 374
             test_filename='test.cmm',
    

  • testsuite/driver/runtests.py
    ... ... @@ -28,7 +28,7 @@ import subprocess
    28 28
     
    
    29 29
     import asyncio
    
    30 30
     
    
    31
    -from testutil import getStdout, str_warn, str_info, print_table, shorten_metric_name, str_removeprefix
    
    31
    +from testutil import discover_io_managers, getStdout, str_warn, str_info, print_table, shorten_metric_name, str_removeprefix
    
    32 32
     from testglobals import getConfig, ghc_env, TestConfig, t, \
    
    33 33
                             TestOptions, brokens, PerfMetric
    
    34 34
     from my_typing import TestName
    

  • testsuite/driver/testglobals.py
    ... ... @@ -126,6 +126,15 @@ class TestConfig:
    126 126
             self.way_flags = {}  # type: Dict[WayName, List[str]]
    
    127 127
             self.way_rts_flags = {}  # type: Dict[WayName, List[str]]
    
    128 128
     
    
    129
    +        # I/O managers (GHC's `--io-manager` RTS flag) supported by the threaded runtime.
    
    130
    +        self.io_managers_threaded = []  # type: List[str]
    
    131
    +
    
    132
    +        # I/O managers (GHC's `--io-manager` RTS flag) supported by the non-threaded runtime.
    
    133
    +        self.io_managers_nonthreaded = []  # type: List[str]
    
    134
    +
    
    135
    +        # All I/O managers (GHC's `--io-manager` RTS flag)
    
    136
    +        self.io_managers_all = []  # type: List[str]
    
    137
    +
    
    129 138
             # Do we have a functional LLVM toolchain?
    
    130 139
             self.have_llvm = False
    
    131 140
     
    
    ... ... @@ -367,6 +376,14 @@ class TestOptions:
    367 376
            # add these ways to the default set
    
    368 377
            self.extra_ways = [] # type: List[WayName]
    
    369 378
     
    
    379
    +       # IO Managers to test (passed as the --io-manager RTS option)
    
    380
    +       # * [None] - only default IO Manager
    
    381
    +       # * [...] - list of io managers e.g. "mio" or "poll"
    
    382
    +       self.io_managers = [None] # type: List[Optional[str]]
    
    383
    +
    
    384
    +       # IO Manager used in the current test case. Set before each test case. None means default IO Manager
    
    385
    +       self.io_manager_current = None # type: Optional[str]
    
    386
    +
    
    370 387
            # the result we normally expect for this test
    
    371 388
            self.expect = 'pass'
    
    372 389
     
    

  • testsuite/driver/testlib.py
    ... ... @@ -23,7 +23,7 @@ import subprocess
    23 23
     
    
    24 24
     from testglobals import config, ghc_env, default_testopts, brokens, t, \
    
    25 25
                             TestRun, TestResult, TestOptions, PerfMetric
    
    26
    -from testutil import strip_quotes, lndir, link_or_copy_file, passed, \
    
    26
    +from testutil import discover_io_managers, strip_quotes, lndir, link_or_copy_file, passed, \
    
    27 27
                          failBecause, residency_testing_metrics, \
    
    28 28
                          stable_perf_counters, \
    
    29 29
                          PassFail, badResult, str_warn, str_removeprefix
    
    ... ... @@ -565,10 +565,38 @@ def valid_way( way: WayName ) -> bool:
    565 565
     def extra_ways( ways: List[WayName] ):
    
    566 566
         def helper( name: TestName, opts ):
    
    567 567
             _lint_ways(name, ways)
    
    568
    -        opts.extra_ways = [way for way in ways if valid_way(way)]
    
    568
    +        opts.extra_ways += [way for way in ways if valid_way(way) and way not in opts.extra_ways]
    
    569 569
     
    
    570 570
         return helper
    
    571 571
     
    
    572
    +# -----
    
    573
    +
    
    574
    +def _io_managers( name: TestName, opts, io_mgrs: List[str] ) -> None:
    
    575
    +    opts.io_managers = io_mgrs
    
    576
    +
    
    577
    +def io_managers( io_mgrs: List[str] ):
    
    578
    +    """
    
    579
    +    Further split test cases into the given IO Managers if available.
    
    580
    +    Available IO Managers are determined by the test case's way (threaded/non-threads) and OS.
    
    581
    +    You'll likely want to use `extra_ways(['threaded2'])` if you want to test threaded IO Managers (e.g. mio).
    
    582
    +    """
    
    583
    +    return lambda name, opts: _io_managers(name, opts, io_mgrs)
    
    584
    +
    
    585
    +def all_available_io_managers( name: TestName, opts ):
    
    586
    +    """
    
    587
    +    Further split test cases into all available IO Managers.
    
    588
    +    Available IO Managers are determined by the test case's way (threaded/non-threads) and OS.
    
    589
    +    You'll likely want to use `extra_ways(['threaded2'])` to ensure threaded IO Managers are tested (e.g. mio).
    
    590
    +    """
    
    591
    +    _io_managers(name, opts, config.io_managers_all)
    
    592
    +
    
    593
    +def all_available_io_managers_except(exclude: List[str]):
    
    594
    +    """
    
    595
    +    Further split test cases into all available (except the given excluded) IO Managers.
    
    596
    +    Available IO Managers are determined by the test case's way (threaded/non-threads) and OS.
    
    597
    +    You'll likely want to use `extra_ways(['threaded2'])` to ensure threaded IO Managers are tested (e.g. mio).
    
    598
    +    """
    
    599
    +    return lambda name, opts: _io_managers(name, opts, list(set(config.io_managers_all) - set(exclude)))
    
    572 600
     
    
    573 601
     # -----
    
    574 602
     
    
    ... ... @@ -1064,6 +1092,9 @@ def have_dynamic_prof( ) -> bool:
    1064 1092
     def have_profiling( ) -> bool:
    
    1065 1093
         return config.have_profiling
    
    1066 1094
     
    
    1095
    +def have_smp() -> bool:
    
    1096
    +    return config.ghc_has_smp
    
    1097
    +
    
    1067 1098
     def have_threaded( ) -> bool:
    
    1068 1099
         return config.ghc_with_threaded_rts
    
    1069 1100
     
    
    ... ... @@ -1571,6 +1602,15 @@ do_not_copy = ('.hi', '.o', '.dyn_hi'
    1571 1602
                   , '.dyn_o', '.out'
    
    1572 1603
                   ,'.hi-boot', '.o-boot') # 12112
    
    1573 1604
     
    
    1605
    +def way_supports_io_manager(way: WayName, io_manager: Optional[str]) -> bool:
    
    1606
    +    # All ways support a default IO Manager
    
    1607
    +    if io_manager is None:
    
    1608
    +        return True
    
    1609
    +
    
    1610
    +    way_is_threaded = '-threaded' in config.way_flags[way]
    
    1611
    +    available_io_managers = config.io_managers_threaded if way_is_threaded else config.io_managers_nonthreaded
    
    1612
    +    return io_manager in available_io_managers
    
    1613
    +
    
    1574 1614
     async def test_common_work(name: TestName, opts,
    
    1575 1615
                          func, args) -> None:
    
    1576 1616
         try:
    
    ... ... @@ -1619,7 +1659,22 @@ async def test_common_work(name: TestName, opts,
    1619 1659
             # A test itself can request extra ways by setting opts.extra_ways
    
    1620 1660
             all_ways = list(OrderedDict.fromkeys(all_ways + [way for way in opts.extra_ways if way not in all_ways]))
    
    1621 1661
     
    
    1622
    -        t.total_test_cases += len(all_ways)
    
    1662
    +        # We split each way over available io managers
    
    1663
    +        if len(opts.io_managers) == 0:
    
    1664
    +            framework_fail(name, None, 'opts.io_managers is empty')
    
    1665
    +        all_ways_and_io_managers = dict(
    
    1666
    +            (
    
    1667
    +                way,
    
    1668
    +                [io_manager
    
    1669
    +                    for io_manager in opts.io_managers
    
    1670
    +                    if way_supports_io_manager(way, io_manager)
    
    1671
    +                ]
    
    1672
    +            )
    
    1673
    +            for way in all_ways
    
    1674
    +        )
    
    1675
    +
    
    1676
    +        for io_managers in all_ways_and_io_managers.values():
    
    1677
    +            t.total_test_cases += len(io_managers)
    
    1623 1678
     
    
    1624 1679
             only_ways = getTestOpts().only_ways
    
    1625 1680
             ok_way = lambda way: \
    
    ... ... @@ -1702,19 +1757,23 @@ async def test_common_work(name: TestName, opts,
    1702 1757
     
    
    1703 1758
             # Run the required tests...
    
    1704 1759
             for way in do_ways:
    
    1705
    -            if stopping():
    
    1706
    -                break
    
    1707
    -            start = time.monotonic()
    
    1708
    -            try:
    
    1709
    -                await do_test(name, way, func, args, files)
    
    1710
    -            except KeyboardInterrupt:
    
    1711
    -                stopNow()
    
    1712
    -            except Exception as e:
    
    1713
    -                traceback.print_exc()
    
    1714
    -                framework_fail(name, way, traceback.format_exc(),
    
    1715
    -                               runtime=time.monotonic() - start)
    
    1760
    +            for io_manager in all_ways_and_io_managers[way]:
    
    1761
    +                if stopping():
    
    1762
    +                    break
    
    1763
    +                start = time.monotonic()
    
    1764
    +                try:
    
    1765
    +                    getTestOpts().io_manager_current = io_manager
    
    1766
    +                    await do_test(name ,way ,func ,args ,files)
    
    1767
    +                except KeyboardInterrupt:
    
    1768
    +                    stopNow()
    
    1769
    +                except Exception as e:
    
    1770
    +                    traceback.print_exc()
    
    1771
    +                    framework_fail(name, way, traceback.format_exc(),
    
    1772
    +                                runtime=time.monotonic() - start)
    
    1773
    +
    
    1774
    +        for skipped_way in set(all_ways) - set(do_ways):
    
    1775
    +            t.n_tests_skipped += len(all_ways_and_io_managers[skipped_way])
    
    1716 1776
     
    
    1717
    -        t.n_tests_skipped += len(set(all_ways) - set(do_ways))
    
    1718 1777
             if getTestOpts().expect == 'missing-lib': t.n_missing_libs += 1
    
    1719 1778
     
    
    1720 1779
             if config.cleanup and do_ways:
    
    ... ... @@ -1741,6 +1800,8 @@ async def do_test(name: TestName,
    1741 1800
         start = time.monotonic()
    
    1742 1801
     
    
    1743 1802
         full_name = name + '(' + way + ')'
    
    1803
    +    if opts.io_manager_current is not None:
    
    1804
    +        full_name += ' (IO Manager: {})'.format(opts.io_manager_current)
    
    1744 1805
         test_n = len(allTestNames)
    
    1745 1806
         progress_args = [ full_name, t.total_tests, test_n,
    
    1746 1807
             [len(t.unexpected_passes),
    
    ... ... @@ -2140,7 +2201,7 @@ async def compile_grep_core(name: TestName,
    2140 2201
     
    
    2141 2202
     async def compile_and_run__(name: TestName,
    
    2142 2203
                           way: WayName,
    
    2143
    -                      top_mod: Path | None,
    
    2204
    +                      top_mod: Optional[Path],
    
    2144 2205
                           extra_mods: List[str],
    
    2145 2206
                           extra_hc_opts: str,
    
    2146 2207
                           backpack: bool=False,
    
    ... ... @@ -2423,6 +2484,9 @@ async def simple_run(name: TestName, way: WayName, prog: str, extra_run_opts: st
    2423 2484
     
    
    2424 2485
         my_rts_flags = rts_flags(way)
    
    2425 2486
     
    
    2487
    +    if opts.io_manager_current is not None:
    
    2488
    +        my_rts_flags += ' +RTS --io-manager={0} -RTS '.format(opts.io_manager_current)
    
    2489
    +
    
    2426 2490
         # Collect runtime stats if necessary:
    
    2427 2491
         # isStatsTest and not isCompilerStatsTest():
    
    2428 2492
         #   assume we are running a ghc compiled program. Collect stats.
    
    ... ... @@ -2448,6 +2512,12 @@ async def simple_run(name: TestName, way: WayName, prog: str, extra_run_opts: st
    2448 2512
     
    
    2449 2513
         output_file = perfStatsFile(False, name)
    
    2450 2514
     
    
    2515
    +    # Check that IO Manager is available
    
    2516
    +    if opts.io_manager_current is not None:
    
    2517
    +        available_io_managers = discover_io_managers(prog, opts.testdir)
    
    2518
    +        if opts.io_manager_current not in available_io_managers:
    
    2519
    +            framework_fail(name, way, 'IO Manager {} unexpectedly unavailable (available IO Managers: {}). `-non-threaded`/`-threaded` is only supported through way RTS flags (not manually added via test modifiers/args) when testing over IO Managers.'.format(opts.io_manager_current, available_io_managers))
    
    2520
    +
    
    2451 2521
         # run the command
    
    2452 2522
         exit_code = await runCmdPerf(opts.compiler_perf_counters, cmd, output_file,
    
    2453 2523
             working_dir=opts.testdir,
    
    ... ... @@ -2509,7 +2579,7 @@ def rts_flags(way: WayName) -> str:
    2509 2579
     async def interpreter_run(name: TestName,
    
    2510 2580
                         way: WayName,
    
    2511 2581
                         extra_hc_opts: str,
    
    2512
    -                    top_mod: Path | None
    
    2582
    +                    top_mod: Optional[Path]
    
    2513 2583
                         ) -> PassFail:
    
    2514 2584
         opts = getTestOpts()
    
    2515 2585
     
    

  • testsuite/driver/testutil.py
    1 1
     import os
    
    2
    +import re
    
    2 3
     import subprocess
    
    3 4
     import shutil
    
    4 5
     import tempfile
    
    ... ... @@ -179,3 +180,19 @@ def shorten_metric_name(name: str) -> str:
    179 180
             "binary_size/if_compression(3)": "size/3",
    
    180 181
         }
    
    181 182
         return dic.get(name, name)
    
    183
    +
    
    184
    +# I/O manager discovery for a haskell binary. Returns the set of IO Manager
    
    185
    +# strings supported by the binary
    
    186
    +def discover_io_managers(hs_exe: Union[str, Path], cwd: Union[str, Path]) -> Set[str]:
    
    187
    +    rp = subprocess.run(
    
    188
    +        [str(hs_exe), '+RTS', '--info', '-RTS'],
    
    189
    +        cwd=cwd,
    
    190
    +        stdout=subprocess.PIPE,
    
    191
    +        stderr=subprocess.DEVNULL)
    
    192
    +    stdout = rp.stdout.decode('utf-8', errors='replace')
    
    193
    +
    
    194
    +    m = re.search(r'\("I/O managers", "(.*)"\)', stdout)
    
    195
    +    if not m:
    
    196
    +        raise Exception('Expected stdout output of form \'... ("I/O managers", "select selectbis poll") ...\' but got:\n{}'.format(stdout))
    
    197
    +
    
    198
    +    return set(m.group(1).split())

  • testsuite/tests/cmm/should_run/all.T
    ... ... @@ -56,7 +56,7 @@ test('T25601',
    56 56
     test('T27447',
    
    57 57
          [ req_cmm
    
    58 58
          , extra_ways(['optasm'])
    
    59
    -     , when(have_llvm(), extra_ways(['optasm', 'optllvm']))
    
    59
    +     , when(have_llvm(), extra_ways(['optllvm']))
    
    60 60
          ],
    
    61 61
          multi_compile_and_run,
    
    62 62
          ['T27447', [('T27447_cmm.cmm', '')], ''])

  • testsuite/tests/primops/should_run/all.T
    ... ... @@ -41,9 +41,9 @@ test('T14664', normal, compile_and_run, [''])
    41 41
     test('CStringLength', normal, compile_and_run, ['-O2'])
    
    42 42
     test('NonNativeSwitch', normal, compile_and_run, ['-O2'])
    
    43 43
     test('Sized', [
    
    44
    +      extra_ways(['ghci','ghci-opt']),
    
    44 45
           # optasm is broken with js backend: #25922
    
    45
    -      extra_ways(['optasm','ghci','ghci-opt']) if not js_arch()
    
    46
    -        else extra_ways(['ghci','ghci-opt'])
    
    46
    +      when(not js_arch(), extra_ways(['optasm']))
    
    47 47
         ], compile_and_run, [''])
    
    48 48
     
    
    49 49
     test('LevPolyPtrEquality1', normal, compile_and_run, [''])
    

  • testsuite/tests/rts/all.T
    ... ... @@ -674,8 +674,10 @@ test('T23142', [unless(debug_rts(), skip), req_interp], makefile_test, ['T23142'
    674 674
     test('T23400', [], compile_and_run, ['-with-rtsopts -A8k'])
    
    675 675
     
    
    676 676
     test('IOManager', [js_skip, when(arch('wasm32'), skip), when(opsys('mingw32'), skip),
    
    677
    -                   pre_cmd('$MAKE -s --no-print-directory IOManager.hs')],
    
    678
    -                  compile_and_run, [''])
    
    677
    +                   pre_cmd('$MAKE -s --no-print-directory IOManager.hs'),
    
    678
    +                   when(have_smp(), extra_ways(['threaded2'])),
    
    679
    +                   all_available_io_managers_except('winio')],
    
    680
    +                   compile_and_run, [''])
    
    679 681
     
    
    680 682
     test('T24142', [req_target_smp], compile_and_run, ['-threaded -with-rtsopts "-N2"'])
    
    681 683