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

Commits:

4 changed files:

Changes:

  • testsuite/config/ghc
    ... ... @@ -315,6 +315,67 @@ def get_compiler_info():
    315 315
             config.ghc_th_way       = "normal"
    
    316 316
             config.ghc_th_hi_ext    = "hi"
    
    317 317
     
    
    318
    +    # I/O manager discovery.
    
    319
    +    #
    
    320
    +    # Which I/O managers (GHC's `--io-manager` RTS flag) a given RTS build
    
    321
    +    # supports depends on the target platform and on whether the RTS is the
    
    322
    +    # threaded or non-threaded flavour (see rts/IOManager.h's
    
    323
    +    # IOMGR_ENABLED_* macros). Rather than duplicate that platform/way
    
    324
    +    # dependent logic here, we ask the RTS itself: giving it a bogus
    
    325
    +    # '--io-manager=' choice makes it print, on stderr, the list of I/O
    
    326
    +    # managers it was actually built to support (see IOManager.h's
    
    327
    +    # IOMGRS_ENABLED_STR, used when reporting the error in RtsFlags.c),
    
    328
    +    # which we then parse out.
    
    329
    +    def discover_io_managers(extra_flags):
    
    330
    +        import tempfile
    
    331
    +
    
    332
    +        test_src = 'module Main where\nmain :: IO ()\nmain = return ()\n'
    
    333
    +
    
    334
    +        with tempfile.TemporaryDirectory() as d:
    
    335
    +            src = Path(d) / 'IOMgrProbe.hs'
    
    336
    +            src.write_text(test_src)
    
    337
    +            exe = Path(d) / ('ioMgrProbe' + config.exeext)
    
    338
    +
    
    339
    +            # -rtsopts is needed so that the resulting binary will even
    
    340
    +            # consider an --io-manager= flag (rather than immediately
    
    341
    +            # rejecting all "unsafe" RTS options).
    
    342
    +            cmd = '{} -v0 -rtsopts -o {} {} {}'.format(
    
    343
    +                                    config.compiler,
    
    344
    +                                    exe,
    
    345
    +                                    src,
    
    346
    +                                    ' '.join(extra_flags))
    
    347
    +            cp = subprocess.run(
    
    348
    +                cmd,
    
    349
    +                shell=True,
    
    350
    +                cwd=d,
    
    351
    +                stdout=subprocess.DEVNULL,
    
    352
    +                stderr=None if config.verbose >= 3 else subprocess.DEVNULL)
    
    353
    +            if cp.returncode != 0 or not exe.exists():
    
    354
    +                raise Exception("Error in testsuite/config/ghc.discover_io_managers: failed to compile with: {}".format(cmd))
    
    355
    +
    
    356
    +            # An unrecognised '--io-manager=' choice makes the RTS print
    
    357
    +            # the ones it does support, then exit; we don't care that
    
    358
    +            # the program itself never gets to run main.
    
    359
    +            rp = subprocess.run(
    
    360
    +                [str(exe), '+RTS', '--io-manager=not-a-real-io-manager', '-RTS'],
    
    361
    +                cwd=d,
    
    362
    +                stdout=subprocess.DEVNULL,
    
    363
    +                stderr=subprocess.PIPE)
    
    364
    +            stderr = rp.stderr.decode('utf-8', errors='replace')
    
    365
    +
    
    366
    +        m = re.search(r'choices are:\s*(.*)', stderr)
    
    367
    +        if not m:
    
    368
    +            raise Exception("Expected stderr output of form 'choices are: ...' but got: {}".format(stderr));
    
    369
    +        # 'auto' is a meta-choice (let the RTS pick a default for us), not
    
    370
    +        # an I/O manager implementation in its own right.
    
    371
    +        return list(set(m.group(1).split()) - {'auto'})
    
    372
    +
    
    373
    +    config.io_managers_nonthreaded = discover_io_managers([])
    
    374
    +    config.io_managers_threaded = (
    
    375
    +        discover_io_managers(['-threaded']) if config.ghc_with_threaded_rts
    
    376
    +        else [])
    
    377
    +    config.io_managers_all = list(set(config.io_managers_nonthreaded).union(set(config.io_managers_threaded)))
    
    378
    +
    
    318 379
         config.cmm_cpp_is_gcc = test_compile(
    
    319 380
             ['-c'],
    
    320 381
             test_filename='test.cmm',
    

  • 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
     
    
    ... ... @@ -366,6 +375,11 @@ class TestOptions:
    366 375
            # add these ways to the default set
    
    367 376
            self.extra_ways = [] # type: List[WayName]
    
    368 377
     
    
    378
    +       # IO Managers to test
    
    379
    +       # * None - only default IO Manager
    
    380
    +       # * [...] - list of io managers e.g. "mio" or "poll"
    
    381
    +       self.io_managers = [] # type: None | List[str]
    
    382
    +
    
    369 383
            # the result we normally expect for this test
    
    370 384
            self.expect = 'pass'
    
    371 385
     
    

  • testsuite/driver/testlib.py
    ... ... @@ -569,6 +569,33 @@ def extra_ways( ways: List[WayName] ):
    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
    +    Run this test once for each of the given I/O managers (identified by
    
    580
    +    the name used with GHC's `--io-manager` RTS flag, e.g. 'mio',
    
    581
    +    'select', 'winio'), instead of the usual set of ways. I/O managers
    
    582
    +    that are not supported on the current platform/build are silently
    
    583
    +    skipped.
    
    584
    +    """
    
    585
    +    return lambda name, opts: _io_managers(name, opts, io_mgrs)
    
    586
    +
    
    587
    +def all_io_managers( name: TestName, opts ):
    
    588
    +    """
    
    589
    +    Run this test once for each I/O manager supported by this
    
    590
    +    platform/build.
    
    591
    +    """
    
    592
    +    _io_managers(name, opts, config.io_managers_all)
    
    593
    +
    
    594
    +def all_io_managers_except(exclude: List[str]):
    
    595
    +    """
    
    596
    +    Like all_io_managers, but excluding the given I/O managers.
    
    597
    +    """
    
    598
    +    return lambda name, opts: _io_managers(name, opts, list(set(config.io_managers_all) - set(exclude)))
    
    572 599
     
    
    573 600
     # -----
    
    574 601
     
    
    ... ... @@ -1573,6 +1600,12 @@ do_not_copy = ('.hi', '.o', '.dyn_hi'
    1573 1600
                   , '.dyn_o', '.out'
    
    1574 1601
                   ,'.hi-boot', '.o-boot') # 12112
    
    1575 1602
     
    
    1603
    +def way_supports_io_manager(way: WayName, io_manager: None | str) -> bool:
    
    1604
    +    if not io_manager:
    
    1605
    +        return True
    
    1606
    +    way_is_threaded = '-threaded' in config.way_rts_flags[way]
    
    1607
    +    return io_manager in config.io_managers_threaded if way_is_threaded else io_manager in config.io_managers_nonthreaded
    
    1608
    +
    
    1576 1609
     async def test_common_work(name: TestName, opts,
    
    1577 1610
                          func, args) -> None:
    
    1578 1611
         try:
    
    ... ... @@ -1582,11 +1615,14 @@ async def test_common_work(name: TestName, opts,
    1582 1615
             package_conf_cache_file_start_timestamp = get_package_cache_timestamp()
    
    1583 1616
     
    
    1584 1617
             # All the ways we might run this test
    
    1618
    +        func_has_io_manager_param = False
    
    1585 1619
             if func == compile or func == multimod_compile:
    
    1586 1620
                 all_ways = config.compile_ways
    
    1587 1621
             elif func in [compile_and_run, multi_compile_and_run, multimod_compile_and_run]:
    
    1588 1622
                 all_ways = config.run_ways
    
    1623
    +            func_has_io_manager_param = True
    
    1589 1624
             elif func == ghci_script or func == ghci_multiunit_script:
    
    1625
    +            func_has_io_manager_param = True
    
    1590 1626
                 if config.have_interp:
    
    1591 1627
                     all_ways = [WayName('ghci'), WayName('ghci-opt')]
    
    1592 1628
                 else:
    
    ... ... @@ -1616,12 +1652,28 @@ async def test_common_work(name: TestName, opts,
    1616 1652
                 if needsTargetWrapper():
    
    1617 1653
                     opts.skip = True
    
    1618 1654
             else:
    
    1655
    +            if func in [backpack_run, warn_and_run]:
    
    1656
    +                func_has_io_manager_param = True
    
    1619 1657
                 all_ways = [WayName('normal')]
    
    1620 1658
     
    
    1621 1659
             # A test itself can request extra ways by setting opts.extra_ways
    
    1622 1660
             all_ways = list(OrderedDict.fromkeys(all_ways + [way for way in opts.extra_ways if way not in all_ways]))
    
    1623 1661
     
    
    1624
    -        t.total_test_cases += len(all_ways)
    
    1662
    +        # We run each way over each io manager
    
    1663
    +        all_io_managers = opts.io_managers if func_has_io_manager_param and opts.io_managers else [None]
    
    1664
    +        all_ways_and_io_managers = dict(
    
    1665
    +            (
    
    1666
    +                way,
    
    1667
    +                [io_manager
    
    1668
    +                    for io_manager in all_io_managers
    
    1669
    +                    if way_supports_io_manager(way, io_manager)
    
    1670
    +                ]
    
    1671
    +            )
    
    1672
    +            for way in all_ways
    
    1673
    +        )
    
    1674
    +
    
    1675
    +        for io_managers in all_ways_and_io_managers.values():
    
    1676
    +            t.total_test_cases += len(io_managers)
    
    1625 1677
     
    
    1626 1678
             only_ways = getTestOpts().only_ways
    
    1627 1679
             ok_way = lambda way: \
    
    ... ... @@ -1704,17 +1756,26 @@ async def test_common_work(name: TestName, opts,
    1704 1756
     
    
    1705 1757
             # Run the required tests...
    
    1706 1758
             for way in do_ways:
    
    1707
    -            if stopping():
    
    1708
    -                break
    
    1709
    -            try:
    
    1710
    -                await do_test(name, way, func, args, files)
    
    1711
    -            except KeyboardInterrupt:
    
    1712
    -                stopNow()
    
    1713
    -            except Exception as e:
    
    1714
    -                traceback.print_exc()
    
    1715
    -                framework_fail(name, way, traceback.format_exc())
    
    1759
    +            for io_manager in all_ways_and_io_managers[way]:
    
    1760
    +                if stopping():
    
    1761
    +                    break
    
    1762
    +                try:
    
    1763
    +                    await do_test(
    
    1764
    +                        name,
    
    1765
    +                        way,
    
    1766
    +                        io_manager,
    
    1767
    +                        func,
    
    1768
    +                        args,
    
    1769
    +                        files)
    
    1770
    +                except KeyboardInterrupt:
    
    1771
    +                    stopNow()
    
    1772
    +                except Exception as e:
    
    1773
    +                    traceback.print_exc()
    
    1774
    +                    framework_fail(name, way, traceback.format_exc())
    
    1775
    +
    
    1776
    +        for skipped_way in set(all_ways) - set(do_ways):
    
    1777
    +            t.n_tests_skipped += len(all_ways_and_io_managers[skipped_way])
    
    1716 1778
     
    
    1717
    -        t.n_tests_skipped += len(set(all_ways) - set(do_ways))
    
    1718 1779
             if getTestOpts().expect == 'missing-lib': t.n_missing_libs += 1
    
    1719 1780
     
    
    1720 1781
             if config.cleanup and do_ways:
    
    ... ... @@ -1733,6 +1794,7 @@ async def test_common_work(name: TestName, opts,
    1733 1794
     
    
    1734 1795
     async def do_test(name: TestName,
    
    1735 1796
                 way: WayName,
    
    1797
    +            io_manager: None | str,
    
    1736 1798
                 func: Callable[..., Awaitable[PassFail]],
    
    1737 1799
                 args,
    
    1738 1800
                 files: Set[str]
    
    ... ... @@ -1740,6 +1802,8 @@ async def do_test(name: TestName,
    1740 1802
         opts = getTestOpts()
    
    1741 1803
     
    
    1742 1804
         full_name = name + '(' + way + ')'
    
    1805
    +    if io_manager:
    
    1806
    +        full_name += ' (IO Manager: {})'.format(io_manager)
    
    1743 1807
         test_n = len(allTestNames)
    
    1744 1808
         progress_args = [ full_name, t.total_tests, test_n,
    
    1745 1809
             [len(t.unexpected_passes),
    
    ... ... @@ -1820,7 +1884,8 @@ async def do_test(name: TestName,
    1820 1884
                 # Don't continue and try to run the test if the pre_cmd fails.
    
    1821 1885
                 return
    
    1822 1886
     
    
    1823
    -    result = await func(*[name,way] + args)
    
    1887
    +    io_manager_arg = [io_manager] if io_manager else []
    
    1888
    +    result = await func(*[name,way] + io_manager_arg + args)
    
    1824 1889
     
    
    1825 1890
         if opts.expect not in ['pass', 'fail', 'missing-lib']:
    
    1826 1891
             framework_fail(name, way, 'bad expected ' + opts.expect)
    
    ... ... @@ -1909,19 +1974,19 @@ def framework_warn(name: TestName, way: WayName, reason: str) -> None:
    1909 1974
     # run_command.
    
    1910 1975
     
    
    1911 1976
     async def run_command( name, way, cmd ):
    
    1912
    -    return await simple_run( name, '', override_options(cmd), '' )
    
    1977
    +    return await simple_run( name, '', None, override_options(cmd), '' )
    
    1913 1978
     
    
    1914 1979
     async def makefile_test( name, way, target=None ):
    
    1915 1980
         if target is None:
    
    1916 1981
             target = name
    
    1917 1982
     
    
    1918 1983
         cmd = '$MAKE -s --no-print-directory {target}'.format(target=target)
    
    1919
    -    return await run_command(name, way, cmd)
    
    1984
    +    return await run_command(name, way, None, cmd)
    
    1920 1985
     
    
    1921 1986
     # -----------------------------------------------------------------------------
    
    1922 1987
     # GHCi tests
    
    1923 1988
     
    
    1924
    -async def ghci_script( name, way, script):
    
    1989
    +async def ghci_script( name, way, io_manager, script):
    
    1925 1990
         flags = ' '.join(get_compiler_flags())
    
    1926 1991
         way_flags = ' '.join(config.way_flags[way])
    
    1927 1992
     
    
    ... ... @@ -1932,9 +1997,9 @@ async def ghci_script( name, way, script):
    1932 1997
           # NB: put way_flags before flags so that flags in all.T can override others
    
    1933 1998
     
    
    1934 1999
         getTestOpts().stdin = script
    
    1935
    -    return await simple_run( name, way, cmd, getTestOpts().extra_run_opts )
    
    2000
    +    return await simple_run( name, way, io_manager, cmd, getTestOpts().extra_run_opts )
    
    1936 2001
     
    
    1937
    -async def ghci_multiunit_script( name, way, units, script):
    
    2002
    +async def ghci_multiunit_script( name, way, io_manager, units, script):
    
    1938 2003
         flags = ' '.join(get_compiler_flags())
    
    1939 2004
         way_flags = ' '.join(config.way_flags[way])
    
    1940 2005
         unit_flags = ' '.join(['-unit @%s' % unit for unit in units])
    
    ... ... @@ -1946,7 +2011,7 @@ async def ghci_multiunit_script( name, way, units, script):
    1946 2011
           # NB: put way_flags before flags so that flags in all.T can override others
    
    1947 2012
     
    
    1948 2013
         getTestOpts().stdin = script
    
    1949
    -    return await simple_run( name, way, cmd, getTestOpts().extra_run_opts )
    
    2014
    +    return await simple_run( name, way, io_manager, cmd, getTestOpts().extra_run_opts )
    
    1950 2015
     
    
    1951 2016
     # -----------------------------------------------------------------------------
    
    1952 2017
     # Compile-only tests
    
    ... ... @@ -2126,6 +2191,7 @@ async def compile_grep_core(name: TestName,
    2126 2191
     
    
    2127 2192
     async def compile_and_run__(name: TestName,
    
    2128 2193
                           way: WayName,
    
    2194
    +                      io_manager: None | str,
    
    2129 2195
                           top_mod: Path,
    
    2130 2196
                           extra_mods: List[str],
    
    2131 2197
                           extra_hc_opts: str,
    
    ... ... @@ -2168,22 +2234,22 @@ async def compile_and_run__(name: TestName,
    2168 2234
             cmd = './' + name + extension
    
    2169 2235
     
    
    2170 2236
             # we don't check the compiler's stderr for a compile-and-run test
    
    2171
    -        return await simple_run( name, way, cmd, getTestOpts().extra_run_opts )
    
    2237
    +        return await simple_run( name, way, io_manager, cmd, getTestOpts().extra_run_opts )
    
    2172 2238
     
    
    2173
    -async def compile_and_run( name, way, extra_hc_opts ):
    
    2174
    -    return await compile_and_run__( name, way, None, [], extra_hc_opts)
    
    2239
    +async def compile_and_run( name, way, io_manager, extra_hc_opts ):
    
    2240
    +    return await compile_and_run__( name, way, io_manager, None, [], extra_hc_opts)
    
    2175 2241
     
    
    2176
    -async def backpack_run( name, way, extra_hc_opts ):
    
    2177
    -    return await compile_and_run__( name, way, None, [], extra_hc_opts, backpack=True )
    
    2242
    +async def backpack_run( name, way, io_manager, extra_hc_opts ):
    
    2243
    +    return await compile_and_run__( name, way, io_manager, None, [], extra_hc_opts, backpack=True )
    
    2178 2244
     
    
    2179
    -async def multimod_compile_and_run( name, way, top_mod, extra_hc_opts ):
    
    2180
    -    return await compile_and_run__( name, way, top_mod, [], extra_hc_opts)
    
    2245
    +async def multimod_compile_and_run( name, way, io_manager, top_mod, extra_hc_opts ):
    
    2246
    +    return await compile_and_run__( name, way, io_manager, top_mod, [], extra_hc_opts)
    
    2181 2247
     
    
    2182
    -async def multi_compile_and_run( name, way, top_mod, extra_mods, extra_hc_opts ):
    
    2183
    -    return await compile_and_run__( name, way, top_mod, extra_mods, extra_hc_opts)
    
    2248
    +async def multi_compile_and_run( name, way, io_manager, top_mod, extra_mods, extra_hc_opts ):
    
    2249
    +    return await compile_and_run__( name, way, io_manager, top_mod, extra_mods, extra_hc_opts)
    
    2184 2250
     
    
    2185
    -async def warn_and_run( name, way, extra_hc_opts ):
    
    2186
    -    return await compile_and_run__( name, way, None, [], extra_hc_opts, compile_stderr = True)
    
    2251
    +async def warn_and_run( name, way, io_manager, extra_hc_opts ):
    
    2252
    +    return await compile_and_run__( name, way, io_manager, None, [], extra_hc_opts, compile_stderr = True)
    
    2187 2253
     
    
    2188 2254
     async def static_stats( name, way ):
    
    2189 2255
         opts = getTestOpts()
    
    ... ... @@ -2390,7 +2456,7 @@ async def simple_build(name: Union[TestName, str],
    2390 2456
     # from /dev/null.  Route output to testname.run.stdout and
    
    2391 2457
     # testname.run.stderr.  Returns the exit code of the run.
    
    2392 2458
     
    
    2393
    -async def simple_run(name: TestName, way: WayName, prog: str, extra_run_opts: str) -> Any:
    
    2459
    +async def simple_run(name: TestName, way: WayName, io_manager: None | str, prog: str, extra_run_opts: str) -> Any:
    
    2394 2460
         opts = getTestOpts()
    
    2395 2461
     
    
    2396 2462
         # figure out what to use for stdin
    
    ... ... @@ -2409,6 +2475,9 @@ async def simple_run(name: TestName, way: WayName, prog: str, extra_run_opts: st
    2409 2475
     
    
    2410 2476
         my_rts_flags = rts_flags(way)
    
    2411 2477
     
    
    2478
    +    if io_manager:
    
    2479
    +        my_rts_flags += '+RTS --io-manager={0} -RTS'.format(io_manager)
    
    2480
    +
    
    2412 2481
         # Collect runtime stats if necessary:
    
    2413 2482
         # isStatsTest and not isCompilerStatsTest():
    
    2414 2483
         #   assume we are running a ghc compiled program. Collect stats.
    

  • testsuite/tests/rts/all.T
    ... ... @@ -661,8 +661,8 @@ test('T23142', [unless(debug_rts(), skip), req_interp], makefile_test, ['T23142'
    661 661
     
    
    662 662
     test('T23400', [], compile_and_run, ['-with-rtsopts -A8k'])
    
    663 663
     
    
    664
    -test('IOManager', [js_skip, when(arch('wasm32'), skip), when(opsys('mingw32'), skip),
    
    665
    -                   pre_cmd('$MAKE -s --no-print-directory IOManager.hs')],
    
    664
    +test('IOManager', [js_skip, when(arch('wasm32'), skip),
    
    665
    +                   pre_cmd('$MAKE -s --no-print-directory IOManager.hs'), all_io_managers_except('winio')],
    
    666 666
                       compile_and_run, [''])
    
    667 667
     
    
    668 668
     test('T24142', [req_target_smp], compile_and_run, ['-threaded -with-rtsopts "-N2"'])