コード例 #1
0
    def setUp(self):
        if self.run_in_temp_dir:
            # Create a temporary directory.
            self.noise = str(random.random())[2:]
            self.temp_root = os.path.join(tempfile.gettempdir(), 'test_cover')
            self.temp_dir = os.path.join(self.temp_root, self.noise)
            os.makedirs(self.temp_dir)
            self.old_dir = os.getcwd()
            os.chdir(self.temp_dir)

            # Modules should be importable from this temp directory.
            self.old_syspath = sys.path[:]
            sys.path.insert(0, '')

            # Keep a counter to make every call to check_coverage unique.
            self.n = 0

        # Record environment variables that we changed with set_environ.
        self.environ_undos = {}

        # Capture stdout and stderr so we can examine them in tests.
        # nose keeps stdout from littering the screen, so we can safely Tee it,
        # but it doesn't capture stderr, so we don't want to Tee stderr to the
        # real stderr, since it will interfere with our nice field of dots.
        self.old_stdout = sys.stdout
        self.captured_stdout = StringIO()
        sys.stdout = Tee(sys.stdout, self.captured_stdout)
        self.old_stderr = sys.stderr
        self.captured_stderr = StringIO()
        sys.stderr = self.captured_stderr

        # Record sys.modules here so we can restore it in tearDown.
        self.old_modules = dict(sys.modules)
コード例 #2
0
    def setUp(self):
        super(CoverageTest, self).setUp()

        if _TEST_NAME_FILE:
            f = open(_TEST_NAME_FILE, "w")
            f.write("%s_%s" % (self.__class__.__name__, self._testMethodName))
            f.close()

        # Tell newer unittest implementations to print long helpful messages.
        self.longMessage = True

        # tearDown will restore the original sys.path
        self.old_syspath = sys.path[:]

        if self.run_in_temp_dir:
            # Create a temporary directory.
            self.noise = str(random.random())[2:]
            self.temp_root = os.path.join(tempfile.gettempdir(), 'test_cover')
            self.temp_dir = os.path.join(self.temp_root, self.noise)
            os.makedirs(self.temp_dir)
            self.old_dir = os.getcwd()
            os.chdir(self.temp_dir)

            # Modules should be importable from this temp directory.  We don't
            # use '' because we make lots of different temp directories and
            # nose's caching importer can get confused.  The full path prevents
            # problems.
            sys.path.insert(0, os.getcwd())

            # Keep a counter to make every call to check_coverage unique.
            self.n = 0

        # Record environment variables that we changed with set_environ.
        self.environ_undos = {}

        # Capture stdout and stderr so we can examine them in tests.
        # nose keeps stdout from littering the screen, so we can safely Tee it,
        # but it doesn't capture stderr, so we don't want to Tee stderr to the
        # real stderr, since it will interfere with our nice field of dots.
        self.old_stdout = sys.stdout
        self.captured_stdout = StringIO()
        sys.stdout = Tee(sys.stdout, self.captured_stdout)
        self.old_stderr = sys.stderr
        self.captured_stderr = StringIO()
        sys.stderr = self.captured_stderr

        # Record sys.modules here so we can restore it in tearDown.
        self.old_modules = dict(sys.modules)

        class_behavior = self.class_behavior()
        class_behavior.tests += 1
        class_behavior.test_method_made_any_files = False
        class_behavior.temp_dir = self.run_in_temp_dir
コード例 #3
0
ファイル: test_helpers.py プロジェクト: clytwynec/coveragepy
    def setUp(self):
        super(StdStreamCapturingMixin, self).setUp()

        # Capture stdout and stderr so we can examine them in tests.
        # nose keeps stdout from littering the screen, so we can safely Tee it,
        # but it doesn't capture stderr, so we don't want to Tee stderr to the
        # real stderr, since it will interfere with our nice field of dots.
        self.old_stdout = sys.stdout
        self.captured_stdout = StringIO()
        sys.stdout = Tee(sys.stdout, self.captured_stdout)
        self.old_stderr = sys.stderr
        self.captured_stderr = StringIO()
        sys.stderr = self.captured_stderr

        self.addCleanup(self.cleanup_std_streams)
コード例 #4
0
ファイル: test_plugins.py プロジェクト: jayhetee/coveragepy
    def test_plugin_sys_info(self):
        self.make_file(
            "plugin_sys_info.py", """\
            import coverage

            class Plugin(coverage.CoveragePlugin):
                def sys_info(self):
                    return [("hello", "world")]

            def coverage_init(reg, options):
                reg.add_noop(Plugin())
            """)
        debug_out = StringIO()
        cov = coverage.Coverage(debug=["sys"])
        cov._debug_file = debug_out
        cov.set_option("run:plugins", ["plugin_sys_info"])
        cov.load()

        out_lines = debug_out.getvalue().splitlines()
        expected_end = [
            "-- sys: plugin_sys_info.Plugin -------------------------------",
            " hello: world",
            "-- end -------------------------------------------------------",
        ]
        self.assertEqual(expected_end, out_lines[-len(expected_end):])
コード例 #5
0
def source_token_lines(source):
    ws_tokens = [token.INDENT, token.DEDENT, token.NEWLINE, tokenize.NL]
    line = []
    col = 0
    source = source.expandtabs(8).replace('\r\n', '\n')
    tokgen = tokenize.generate_tokens(StringIO(source).readline)
    for ttype, ttext, (_, scol), (_, ecol), _ in phys_tokens(tokgen):
        mark_start = True
        for part in re.split('(\n)', ttext):
            if part == '\n':
                yield line
                line = []
                col = 0
                mark_end = False
            elif part == '':
                mark_end = False
            elif ttype in ws_tokens:
                mark_end = False
            else:
                if mark_start and scol > col:
                    line.append(('ws', ' ' * (scol - col)))
                    mark_start = False
                tok_class = tokenize.tok_name.get(ttype, 'xx').lower()[:3]
                if ttype == token.NAME and keyword.iskeyword(ttext):
                    tok_class = 'key'
                line.append((tok_class, part))
                mark_end = True
            scol = 0

        if mark_end:
            col = ecol

    if line:
        yield line
コード例 #6
0
    def test_plugin_sys_info(self):
        self.make_file("plugin_sys_info.py", """\
            import coverage

            class Plugin(coverage.CoveragePlugin):
                def sys_info(self):
                    return [("hello", "world")]

            def coverage_init(reg, options):
                reg.add_file_tracer(Plugin())
            """)
        debug_out = StringIO()
        cov = coverage.Coverage(debug=["sys"])
        cov._debug_file = debug_out
        cov.set_option("run:plugins", ["plugin_sys_info"])
        cov.start()
        cov.stop()      # pragma: nested

        out_lines = [line.strip() for line in debug_out.getvalue().splitlines()]
        if env.C_TRACER:
            assert 'plugins.file_tracers: plugin_sys_info.Plugin' in out_lines
        else:
            assert 'plugins.file_tracers: plugin_sys_info.Plugin (disabled)' in out_lines
        assert 'plugins.configurers: -none-' in out_lines
        expected_end = [
            "-- sys: plugin_sys_info.Plugin -------------------------------",
            "hello: world",
            "-- end -------------------------------------------------------",
            ]
        assert expected_end == out_lines[-len(expected_end):]
コード例 #7
0
ファイル: test_api.py プロジェクト: TamaMcGlinn/coveragepy
    def pretend_to_be_pytestcov(self, append):
        """Act like pytest-cov."""
        self.make_file("prog.py", """\
            a = 1
            b = 2
            if b == 1:
                c = 4
            """)
        self.make_file(".coveragerc", """\
            [run]
            parallel = True
            source = .
            """)

        cov = coverage.Coverage(source=None, branch=None, config_file='.coveragerc')
        if append:
            cov.load()
        else:
            cov.erase()
        self.start_import_stop(cov, "prog")
        cov.combine()
        cov.save()
        report = StringIO()
        cov.report(show_missing=None, ignore_errors=True, file=report, skip_covered=None,
                   skip_empty=None)
        assert report.getvalue() == textwrap.dedent("""\
            Name      Stmts   Miss  Cover
            -----------------------------
            prog.py       4      1    75%
            -----------------------------
            TOTAL         4      1    75%
            """)
        self.assert_file_count(".coverage", 0)
        self.assert_file_count(".coverage.*", 1)
コード例 #8
0
ファイル: codeunit.py プロジェクト: connoryang/1v1dec
 def source_file(self):
     if os.path.exists(self.filename):
         return open_source(self.filename)
     source = self.file_locator.get_zip_data(self.filename)
     if source is not None:
         return StringIO(source)
     raise CoverageException("No source for code '%s'." % self.filename)
コード例 #9
0
ファイル: test_plugins.py プロジェクト: ziplokk1/coveragepy
    def test_plugin_with_no_sys_info(self):
        self.make_file("plugin_no_sys_info.py", """\
            import coverage

            class Plugin(coverage.CoveragePlugin):
                pass

            def coverage_init(reg, options):
                reg.add_configurer(Plugin())
            """)
        debug_out = StringIO()
        cov = coverage.Coverage(debug=["sys"])
        cov._debug_file = debug_out
        cov.set_option("run:plugins", ["plugin_no_sys_info"])
        cov.start()
        cov.stop()      # pragma: nested

        out_lines = [line.strip() for line in debug_out.getvalue().splitlines()]
        self.assertIn('plugins.file_tracers: -none-', out_lines)
        self.assertIn('plugins.configurers: plugin_no_sys_info.Plugin', out_lines)
        expected_end = [
            "-- sys: plugin_no_sys_info.Plugin ----------------------------",
            "-- end -------------------------------------------------------",
            ]
        self.assertEqual(expected_end, out_lines[-len(expected_end):])
コード例 #10
0
    def test_plugin_init(self):
        self.make_file('coveragerc_test_config', '')

        debug_out = StringIO()
        cov = Coverage(config_file='coveragerc_test_config', debug=['sys'])
        cov._debug_file = debug_out
        cov.set_option('run:plugins', ['coverage_config_reload_plugin'])
        cov.start()
        cov.stop()

        out_lines = [
            line.strip() for line in debug_out.getvalue().splitlines()
        ]
        self.assertIn('plugins.file_tracers: -none-', out_lines)

        expected_end = [
            '-- sys: coverage_config_reload_plugin.ConfigReloadPlugin -----',
            'configreload: True',
            '-- end -------------------------------------------------------',
        ]
        self.assertEqual(expected_end, out_lines[-len(expected_end):])

        if LooseVersion(coverage_version) >= LooseVersion('4.6'):
            self.assertIn(
                'plugins.configurers: coverage_config_reload_plugin.ConfigReloadPlugin',
                out_lines)
コード例 #11
0
    def _check_config(self, filename, own_rc):
        section = 'report' if own_rc else 'coverage:report'

        # Force own rc for pre 4.4.1
        if LooseVersion(coverage_version) < LooseVersion('4.4.1'):
            self.make_file(
                filename, """\
                [report]
                ignore_errors = true
                """)
        else:
            self.make_file(
                filename, """\
                [{}]
                ignore_errors = true
                """.format(section))

        debug_out = StringIO()
        cov = Coverage(config_file=filename, debug=['sys'])
        assert cov.config.get_option('report:ignore_errors') is True
        cov._debug_file = debug_out

        self.make_file(
            filename, """\
            [{}]
            ignore_errors = off
            """.format(section))

        cov.set_option('run:plugins', ['coverage_config_reload_plugin'])
        cov.start()
        cov.stop()

        assert cov.config.get_option('report:ignore_errors') is False
コード例 #12
0
 def get_report(self, cov):
     """Get the report from `cov`, and canonicalize it."""
     repout = StringIO()
     cov.report(file=repout, show_missing=False)
     report = repout.getvalue().replace('\\', '/')
     report = re.sub(r" +", " ", report)
     return report
コード例 #13
0
 def source_file(self):
     """Return an open file for reading the source of the code unit."""
     if os.path.exists(self.filename):
         return open_source(self.filename)
     source = self.file_locator.get_zip_data(self.filename)
     if source is not None:
         return StringIO(source)
     raise CoverageException("No source for code '%s'." % self.filename)
コード例 #14
0
ファイル: parser.py プロジェクト: RohithKP/LetterPrinting
 def generate_tokens(self, text):
     """A stand-in for `tokenize.generate_tokens`."""
     if text != self.last_text:
         self.last_text = text
         self.last_tokens = list(
             tokenize.generate_tokens(StringIO(text).readline)
         )
     return self.last_tokens
コード例 #15
0
 def coverage_usepkgs(self, **kwargs):
     """Try coverage.report()."""
     cov = coverage.Coverage()
     cov.start()
     import usepkgs  # pragma: nested   # pylint: disable=import-error, unused-variable
     cov.stop()  # pragma: nested
     report = StringIO()
     cov.report(file=report, **kwargs)
     return report.getvalue()
コード例 #16
0
 def coverage_usepkgs(self, **kwargs):
     """Try coverage.report()."""
     cov = coverage.coverage()
     cov.start()
     import usepkgs  # pragma: nested   # pylint: disable=F0401,W0612
     cov.stop()  # pragma: nested
     report = StringIO()
     cov.report(file=report, **kwargs)
     return report.getvalue()
コード例 #17
0
ファイル: test_data.py プロジェクト: federicobond/coveragepy
    def test_read_and_write_are_opposites(self):
        covdata1 = CoverageData()
        covdata1.add_arcs(ARCS_3)
        stringio = StringIO()
        covdata1.write_fileobj(stringio)

        stringio.seek(0)
        covdata2 = CoverageData()
        covdata2.read_fileobj(stringio)
        self.assert_arcs3_data(covdata2)
コード例 #18
0
 def get_summary_text(self, coverage_data, options):
     """Get text output from the SummaryReporter."""
     cov = Coverage()
     cov.start()
     cov.stop()  # pragma: nested
     cov.data = coverage_data
     printer = SummaryReporter(cov, options)
     destination = StringIO()
     printer.report([], destination)
     return destination.getvalue()
コード例 #19
0
ファイル: test_plugins.py プロジェクト: elimarkov/coveragepy
    def test_defer_to_python(self):
        # A plugin that measures, but then wants built-in python reporting.
        self.make_file(
            "fairly_odd_plugin.py", """\
            # A plugin that claims all the odd lines are executed, and none of
            # the even lines, and then punts reporting off to the built-in
            # Python reporting.
            import coverage.plugin
            class Plugin(coverage.CoveragePlugin):
                def file_tracer(self, filename):
                    return OddTracer(filename)
                def file_reporter(self, filename):
                    return "python"

            class OddTracer(coverage.plugin.FileTracer):
                def __init__(self, filename):
                    self.filename = filename
                def source_filename(self):
                    return self.filename
                def line_number_range(self, frame):
                    lineno = frame.f_lineno
                    if lineno % 2:
                        return (lineno, lineno)
                    else:
                        return (-1, -1)

            def coverage_init(reg, options):
                reg.add_file_tracer(Plugin())
            """)
        self.make_file(
            "unsuspecting.py", """\
            a = 1
            b = 2
            c = 3
            d = 4
            e = 5
            f = 6
            """)
        cov = coverage.Coverage(include=["unsuspecting.py"])
        cov.set_option("run:plugins", ["fairly_odd_plugin"])
        self.start_import_stop(cov, "unsuspecting")

        repout = StringIO()
        total = cov.report(file=repout, show_missing=True)
        report = repout.getvalue().splitlines()
        expected = [
            'Name              Stmts   Miss  Cover   Missing',
            '-----------------------------------------------',
            'unsuspecting.py       6      3    50%   2, 4, 6',
            '-----------------------------------------------',
            'TOTAL                 6      3    50%',
        ]
        assert expected == report
        assert total == 50
コード例 #20
0
 def test_report_file(self):
     # The file= argument of coverage.report makes the report go there.
     self.do_report_work("mycode3")
     fout = StringIO()
     coverage.report(["mycode3.py"], file=fout)
     self.assertEqual(self.stdout(), "")
     self.assertEqual(fout.getvalue(), textwrap.dedent("""\
         Name      Stmts   Miss  Cover   Missing
         ---------------------------------------
         mycode3       7      3    57%   4-6
         """))
コード例 #21
0
    def source_file(self):
        """Return an open file for reading the source of the code unit."""
        if os.path.exists(self.filename):
            # A regular text file: open it.
            return open_source(self.filename)

        # Maybe it's in a zip file?
        source = self.file_locator.get_zip_data(self.filename)
        if source is not None:
            return StringIO(source)

        # Couldn't find source.
        raise CoverageException("No source for code %r." % self.filename)
コード例 #22
0
def source_token_lines(source):
    """Generate a series of lines, one for each line in `source`.
    
    Each line is a list of pairs, each pair is a token::
    
        [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]
    
    Each pair has a token class, and the token text.
    
    If you concatenate all the token texts, and then join them with newlines,
    you should have your original `source` back, with two differences:
    trailing whitespace is not preserved, and a final line with no newline
    is indistinguishable from a final line with a newline.
    
    """
    ws_tokens = [token.INDENT,
     token.DEDENT,
     token.NEWLINE,
     tokenize.NL]
    line = []
    col = 0
    source = source.expandtabs(8).replace('\r\n', '\n')
    tokgen = tokenize.generate_tokens(StringIO(source).readline)
    for ttype, ttext, (_, scol), (_, ecol), _ in phys_tokens(tokgen):
        mark_start = True
        for part in re.split('(\n)', ttext):
            if part == '\n':
                yield line
                line = []
                col = 0
                mark_end = False
            elif part == '':
                mark_end = False
            elif ttype in ws_tokens:
                mark_end = False
            else:
                if mark_start and scol > col:
                    line.append(('ws', ' ' * (scol - col)))
                    mark_start = False
                tok_class = tokenize.tok_name.get(ttype, 'xx').lower()[:3]
                if ttype == token.NAME and keyword.iskeyword(ttext):
                    tok_class = 'key'
                line.append((tok_class, part))
                mark_end = True
            scol = 0

        if mark_end:
            col = ecol

    if line:
        yield line
コード例 #23
0
    def _raw_parse(self):
        """Parse the source to find the interesting facts about its lines.
        
        A handful of member fields are updated.
        
        """
        if self.exclude:
            self.excluded = self.lines_matching(self.exclude)
        indent = 0
        exclude_indent = 0
        excluding = False
        prev_toktype = token.INDENT
        first_line = None
        empty = True
        tokgen = tokenize.generate_tokens(StringIO(self.text).readline)
        for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen:
            if self.show_tokens:
                print '%10s %5s %-20r %r' % (tokenize.tok_name.get(
                    toktype, toktype), nice_pair(
                        (slineno, elineno)), ttext, ltext)
            if toktype == token.INDENT:
                indent += 1
            elif toktype == token.DEDENT:
                indent -= 1
            elif toktype == token.NAME and ttext == 'class':
                self.classdefs.add(slineno)
            elif toktype == token.OP and ttext == ':':
                if not excluding and elineno in self.excluded:
                    exclude_indent = indent
                    excluding = True
            elif toktype == token.STRING and prev_toktype == token.INDENT:
                self.docstrings.update(range(slineno, elineno + 1))
            elif toktype == token.NEWLINE:
                if first_line is not None and elineno != first_line:
                    rng = (first_line, elineno)
                    for l in range(first_line, elineno + 1):
                        self.multiline[l] = rng

                first_line = None
            if ttext.strip() and toktype != tokenize.COMMENT:
                empty = False
                if first_line is None:
                    first_line = slineno
                    if excluding and indent <= exclude_indent:
                        excluding = False
                    if excluding:
                        self.excluded.add(elineno)
            prev_toktype = toktype

        if not empty:
            self.statement_starts.update(self.byte_parser._find_statements())
コード例 #24
0
    def test_empty_files(self):
        # Shows that empty files like __init__.py are listed as having zero
        # statements, not one statement.
        cov = coverage.Coverage(branch=True)
        cov.start()
        import usepkgs  # pragma: nested # pylint: disable=import-error, unused-variable
        cov.stop()  # pragma: nested

        repout = StringIO()
        cov.report(file=repout, show_missing=False)

        report = repout.getvalue().replace('\\', '/')
        report = re.sub(r"\s+", " ", report)
        self.assertIn("tests/modules/pkg1/__init__.py 2 0 0 0 100%", report)
        self.assertIn("tests/modules/pkg2/__init__.py 0 0 0 0 100%", report)
コード例 #25
0
    def test_empty_files(self):
        # Shows that empty files like __init__.py are listed as having zero
        # statements, not one statement.
        cov = coverage.coverage()
        cov.start()
        import usepkgs                      # pylint: disable=F0401,W0612
        cov.stop()

        repout = StringIO()
        cov.report(file=repout, show_missing=False)

        report = repout.getvalue().replace('\\', '/')
        report = re.sub(r"\s+", " ", report)
        self.assert_("test/modules/pkg1/__init__ 1 0 100%" in report)
        self.assert_("test/modules/pkg2/__init__ 0 0 100%" in report)
コード例 #26
0
    def get_summary_text(self, options):
        """Get text output from the SummaryReporter."""
        self.make_rigged_file("file1.py", 339, 155)
        self.make_rigged_file("file2.py", 13, 3)
        self.make_rigged_file("file3.py", 234, 228)
        self.make_file("doit.py", "import file1, file2, file3")

        cov = Coverage(source=["."], omit=["doit.py"])
        cov.start()
        import doit  # pragma: nested # pylint: disable=import-error, unused-import
        cov.stop()  # pragma: nested
        printer = SummaryReporter(cov, options)
        destination = StringIO()
        printer.report([], destination)
        return destination.getvalue()
コード例 #27
0
    def test_should_trace_cache(self):
        # The tracers should only invoke should_trace once for each file name.
        # TODO: Might be better to do this with a mocked _should_trace,
        # rather than by examining debug output.

        # Make some files that invoke each other.
        self.make_file(
            "f1.py", """\
            def f1(x, f):
                return f(x)
            """)

        self.make_file(
            "f2.py", """\
            import f1

            def func(x):
                return f1.f1(x, otherfunc)

            def otherfunc(x):
                return x*x

            for i in range(10):
                func(i)
            """)

        # Trace one file, but not the other, and get the debug output.
        debug_out = StringIO()
        cov = coverage.coverage(include=["f1.py"],
                                debug=['trace'],
                                debug_file=debug_out)

        # Import the python file, executing it.
        self.start_import_stop(cov, "f2")

        # Grab all the filenames mentioned in debug output, there should be no
        # duplicates.
        trace_lines = [
            l for l in debug_out.getvalue().splitlines()
            if l.startswith("Tracing ") or l.startswith("Not tracing ")
        ]
        filenames = [re.search(r"'[^']+'", l).group() for l in trace_lines]
        self.assertEqual(len(filenames), len(set(filenames)))

        # Double-check that the tracing messages are in there somewhere.
        self.assertGreater(len(filenames), 5)
コード例 #28
0
    def f1_debug_output(self, debug):
        """Runs some code with `debug` option, returns the debug output."""
        # Make code to run.
        self.make_file("f1.py", """\
            def f1(x):
                return x+1

            for i in range(5):
                f1(i)
            """)

        debug_out = StringIO()
        cov = coverage.coverage(debug=debug, debug_file=debug_out)
        self.start_import_stop(cov, "f1")

        out_lines = debug_out.getvalue().splitlines()
        return out_lines
コード例 #29
0
    def test_reload_plugin_init(self):
        self._reset_env()

        self.make_file(
            '.coveragerc', """\
            [run]
            plugins = coverage_env_plugin, coverage_config_reload_plugin
            [coverage_env_plugin]
            markers = True
            [report]
            exclude_lines =
              pragma ${OS_NAME}: no cover
            """)

        debug_out = StringIO()
        cov = Coverage(config_file='.coveragerc', debug=['sys'])
        cov._debug_file = debug_out
        cov.set_option(
            'run:plugins',
            ['coverage_env_plugin', 'coverage_config_reload_plugin'])
        cov.start()
        cov.stop()

        assert cov.config.get_option('coverage_env_plugin:markers') == 'True'

        out_lines = [
            line.strip() for line in debug_out.getvalue().splitlines()
        ]
        self.assertIn('plugins.file_tracers: -none-', out_lines)

        if LooseVersion(config_reload_version) >= LooseVersion('0.3.0'):
            expected_end = [
                '-- sys: coverage_config_reload_plugin.ConfigReloadPlugin -----',
                'configreload: True',
                '-- end -------------------------------------------------------',
            ]
            self.assertEqual(expected_end, out_lines[-len(expected_end):])

            if LooseVersion(coverage_version) >= LooseVersion('4.6'):
                self.assertIn(
                    'plugins.configurers: coverage_config_reload_plugin.ConfigReloadPlugin',
                    out_lines)
コード例 #30
0
    def test_plugin_with_no_sys_info(self):
        self.make_file(
            "plugin_no_sys_info.py", """\
            import coverage

            class Plugin(coverage.CoveragePlugin):
                pass
            """)
        debug_out = StringIO()
        cov = coverage.Coverage(debug=["sys"])
        cov._debug_file = debug_out
        cov.config["run:plugins"] = ["plugin_no_sys_info"]
        cov.load()

        out_lines = debug_out.getvalue().splitlines()
        expected_end = [
            "-- sys: plugin_no_sys_info -----------------------------------",
            "-- end -------------------------------------------------------",
        ]
        self.assertEqual(expected_end, out_lines[-len(expected_end):])