def compare_html(expected, actual): """Specialized compare function for our HTML files.""" scrubs = [ (r'/coverage.readthedocs.io/?[-.\w/]*', '/coverage.readthedocs.io/VER'), (r'coverage.py v[\d.abc]+', 'coverage.py vVER'), (r'created at \d\d\d\d-\d\d-\d\d \d\d:\d\d [-+]\d\d\d\d', 'created at DATE'), (r'created at \d\d\d\d-\d\d-\d\d \d\d:\d\d', 'created at DATE'), # Some words are identifiers in one version, keywords in another. (r'<span class="(nam|key)">(print|True|False)</span>', r'<span class="nam">\2</span>'), # Occasionally an absolute path is in the HTML report. (filepath_to_regex(TESTS_DIR), 'TESTS_DIR'), (filepath_to_regex(flat_rootname(unicode_class(TESTS_DIR))), '_TESTS_DIR'), # The temp dir the tests make. (filepath_to_regex(os.getcwd()), 'TEST_TMPDIR'), (filepath_to_regex(flat_rootname(unicode_class(os.getcwd()))), '_TEST_TMPDIR'), (filepath_to_regex(abs_file(os.getcwd())), 'TEST_TMPDIR'), (filepath_to_regex(flat_rootname(unicode_class(abs_file( os.getcwd())))), '_TEST_TMPDIR'), (r'/private/var/folders/[\w/]{35}/coverage_test/tests_test_html_\w+_\d{8}', 'TEST_TMPDIR'), (r'_private_var_folders_\w{35}_coverage_test_tests_test_html_\w+_\d{8}', '_TEST_TMPDIR'), ] if env.WINDOWS: # For file paths... scrubs += [(r"\\", "/")] compare(expected, actual, file_pattern="*.html", scrubs=scrubs)
def compare_html(expected, actual, extra_scrubs=None): """Specialized compare function for our HTML files.""" __tracebackhide__ = True # pytest, please don't show me this function. scrubs = [ (r'/coverage.readthedocs.io/?[-.\w/]*', '/coverage.readthedocs.io/VER'), (r'coverage.py v[\d.abc]+', 'coverage.py vVER'), (r'created at \d\d\d\d-\d\d-\d\d \d\d:\d\d [-+]\d\d\d\d', 'created at DATE'), (r'created at \d\d\d\d-\d\d-\d\d \d\d:\d\d', 'created at DATE'), # Occasionally an absolute path is in the HTML report. (filepath_to_regex(TESTS_DIR), 'TESTS_DIR'), (filepath_to_regex(flat_rootname(str(TESTS_DIR))), '_TESTS_DIR'), # The temp dir the tests make. (filepath_to_regex(os.getcwd()), 'TEST_TMPDIR'), (filepath_to_regex(flat_rootname(str(os.getcwd()))), '_TEST_TMPDIR'), (filepath_to_regex(abs_file(os.getcwd())), 'TEST_TMPDIR'), (filepath_to_regex(flat_rootname(str(abs_file(os.getcwd())))), '_TEST_TMPDIR'), (r'/private/var/[\w/]+/pytest-of-\w+/pytest-\d+/(popen-gw\d+/)?t\d+', 'TEST_TMPDIR'), ] if env.WINDOWS: # For file paths... scrubs += [(r"\\", "/")] if extra_scrubs: scrubs += extra_scrubs compare(expected, actual, file_pattern="*.html", scrubs=scrubs)
def make_data_file(): data = coverage.CoverageData(".coverage.1") data.add_lines( {abs_file('ci/girder/g1.py'): dict.fromkeys(range(10))}) data.add_lines({ abs_file('ci/girder/plugins/p1.py'): dict.fromkeys(range(10)) }) data.write()
def test_filepath_contains_absolute_prefix_twice(self): # https://bitbucket.org/ned/coveragepy/issue/194 # Build a path that has two pieces matching the absolute path prefix. # Technically, this test doesn't do that on Windows, but drive # letters make that impractical to achieve. files.set_relative_directory() d = abs_file(os.curdir) trick = os.path.splitdrive(d)[1].lstrip(os.path.sep) rel = os.path.join('sub', trick, 'file1.py') self.assertEqual(files.relative_filename(abs_file(rel)), rel)
def test_combine_with_aliases(self): self.make_file("d1/x.py", """\ a = 1 b = 2 print(f"{a} {b}") """) self.make_file("d2/x.py", """\ # 1 # 2 # 3 c = 4 d = 5 print(f"{c} {d}") """) self.make_file(".coveragerc", """\ [run] source = . parallel = True [paths] source = src */d1 */d2 """) out = self.run_command("coverage run " + os.path.normpath("d1/x.py")) assert out == '1 2\n' out = self.run_command("coverage run " + os.path.normpath("d2/x.py")) assert out == '4 5\n' self.assert_file_count(".coverage.*", 2) self.run_command("coverage combine") self.assert_exists(".coverage") # After combining, there should be only the .coverage file. self.assert_file_count(".coverage.*", 0) # Read the coverage data file and see that the two different x.py # files have been combined together. data = coverage.CoverageData() data.read() summary = line_counts(data, fullpath=True) assert len(summary) == 1 actual = abs_file(list(summary.keys())[0]) expected = abs_file('src/x.py') assert expected == actual assert list(summary.values())[0] == 6
def cached_abs_file(self, filename): """A locally cached version of `abs_file`.""" key = (type(filename), filename) try: return self.abs_file_cache[key] except KeyError: return self.abs_file_cache.setdefault(key, abs_file(filename))
def _get_file_reporter(self, morf): """Get a FileReporter for a module or filename.""" plugin = None if isinstance(morf, string_class): abs_morf = abs_file(morf) plugin_name = self.data.plugin_data().get(abs_morf) if plugin_name: plugin = self.plugins.get(plugin_name) if plugin: file_reporter = plugin.file_reporter(abs_morf) if file_reporter is None: raise CoverageException( "Plugin %r did not provide a file reporter for %r." % ( plugin._coverage_plugin_name, morf ) ) else: file_reporter = PythonFileReporter(morf, self) # The FileReporter can have a name attribute, but if it doesn't, we'll # supply it as the relative path to self.filename. if not hasattr(file_reporter, "name"): file_reporter.name = files.relative_filename(file_reporter.filename) return file_reporter
def _get_file_reporter(self, morf): """Get a FileReporter for a module or file name.""" plugin = None file_reporter = "python" if isinstance(morf, string_class): abs_morf = abs_file(morf) plugin_name = self.data.file_tracer(abs_morf) if plugin_name: plugin = self.plugins.get(plugin_name) if plugin: file_reporter = plugin.file_reporter(abs_morf) if file_reporter is None: raise CoverageException( "Plugin %r did not provide a file reporter for %r." % ( plugin._coverage_plugin_name, morf ) ) if file_reporter == "python": # pylint: disable=redefined-variable-type file_reporter = PythonFileReporter(morf, self) return file_reporter
def _get_file_reporter(self, morf): """Get a FileReporter for a module or filename.""" plugin = None if isinstance(morf, string_class): abs_morf = abs_file(morf) plugin_name = self.data.plugin_data().get(abs_morf) if plugin_name: plugin = self.plugins.get(plugin_name) if plugin: file_reporter = plugin.file_reporter(abs_morf) if file_reporter is None: raise CoverageException( "Plugin %r did not provide a file reporter for %r." % ( plugin._coverage_plugin_name, morf ) ) else: file_reporter = PythonFileReporter(morf, self) # The FileReporter can have a name attribute, but if it doesn't, we'll # supply it as the relative path to self.filename. if not hasattr(file_reporter, "name"): file_reporter.name = self.file_locator.relative_filename( file_reporter.filename ) return file_reporter
def test_extensionless_file_collides_with_extension(self): # It used to be that "program" and "program.py" would both be reported # to "program.html". Now they are not. # https://github.com/nedbat/coveragepy/issues/69 self.make_file("program", "import program\n") self.make_file("program.py", "a = 1\n") self.make_data_file(lines={ abs_file("program"): [1], abs_file("program.py"): [1], }) cov = coverage.Coverage() cov.load() cov.html_report() self.assert_exists("htmlcov/index.html") self.assert_exists("htmlcov/program.html") self.assert_exists("htmlcov/program_py.html")
def test_dothtml_not_python(self): # Run an "HTML" file self.make_file("innocuous.html", "a = 3") self.make_data_file(lines={abs_file("innocuous.html"): [1]}) # Before reporting, change it to be an HTML file. self.make_file("innocuous.html", "<h1>This isn't python at all!</h1>") cov = coverage.Coverage() cov.load() with pytest.raises(NoDataError, match="No data to report."): cov.html_report()
def test_accented_dot_py(self): # Make a file with a non-ascii character in the filename. self.make_file("h\xe2t.py", "print('accented')") self.make_data_file(lines={abs_file("h\xe2t.py"): [1]}) cov = coverage.Coverage() cov.load() cov.html_report() self.assert_exists("htmlcov/h\xe2t_py.html") with open("htmlcov/index.html") as indexf: index = indexf.read() assert '<a href="hât_py.html">hât.py</a>' in index
def test_accented_dot_py(self): # Make a file with a non-ascii character in the filename. self.make_file("h\xe2t.py", "print('accented')") self.make_data_file(lines={abs_file("h\xe2t.py"): [1]}) cov = coverage.Coverage() cov.load() cov.xml_report() # The XML report is always UTF8-encoded. with open("coverage.xml", "rb") as xmlf: xml = xmlf.read() assert ' filename="h\xe2t.py"'.encode() in xml assert ' name="h\xe2t.py"'.encode() in xml
def setUp(self): super().setUp() self.make_file("forty_two_plus.py", """\ # I have 42.857% (3/7) coverage! a = 1 b = 2 if a > 3: b = 4 c = 5 d = 6 e = 7 """) self.make_data_file(lines={abs_file("forty_two_plus.py"): [2, 3, 4]})
def test_report_99p9_is_not_ok(self): # A file with 99.9% coverage: self.make_file("ninety_nine_plus.py", "a = 1\n" + "b = 2\n" * 2000 + "if a > 3:\n" + " c = 4\n" ) self.make_data_file(lines={abs_file("ninety_nine_plus.py"): range(1, 2002)}) st, out = self.run_command_status("coverage report --fail-under=100") assert st == 2 expected = "Coverage failure: total of 99 is less than fail-under=100" assert expected == self.last_line_squeezed(out)
def test_accented_directory(self): # Make a file with a non-ascii character in the directory name. self.make_file("\xe2/accented.py", "print('accented')") self.make_data_file(lines={abs_file("\xe2/accented.py"): [1]}) report_expected = ("Name Stmts Miss Cover\n" + "-----------------------------------\n" + "\xe2/accented.py 1 0 100%\n" + "-----------------------------------\n" + "TOTAL 1 0 100%\n") cov = coverage.Coverage() cov.load() output = self.get_report(cov, squeeze=False) assert output == report_expected
def test_accented_directory(self): # Make a file with a non-ascii character in the directory name. self.make_file("\xe2/accented.py", "print('accented')") self.make_data_file(lines={abs_file("\xe2/accented.py"): [1]}) # The HTML report uses ascii-encoded HTML entities. cov = coverage.Coverage() cov.load() cov.html_report() self.assert_exists("htmlcov/d_5786906b6f0ffeb4_accented_py.html") with open("htmlcov/index.html") as indexf: index = indexf.read() expected = '<a href="d_5786906b6f0ffeb4_accented_py.html">â%saccented.py</a>' assert expected % os.sep in index
def try_some_code(self, code, concurrency, the_module, expected_out=None): """Run some concurrency testing code and see that it was all covered. `code` is the Python code to execute. `concurrency` is the name of the concurrency regime to test it under. `the_module` is the imported module that must be available for this to work at all. `expected_out` is the text we expect the code to produce. """ self.make_file("try_it.py", code) cmd = "coverage run --concurrency=%s try_it.py" % concurrency out = self.run_command(cmd) if not the_module: # We don't even have the underlying module installed, we expect # coverage to alert us to this fact. expected_out = ( "Couldn't trace with concurrency=%s, " "the module isn't installed.\n" % concurrency ) self.assertEqual(out, expected_out) elif env.C_TRACER or concurrency == "thread": # We can fully measure the code if we are using the C tracer, which # can support all the concurrency, or if we are using threads. if expected_out is None: expected_out = "%d\n" % (sum(range(self.LIMIT))) self.assertEqual(out, expected_out) # Read the coverage file and see that try_it.py has all its lines # executed. data = coverage.CoverageData() data.read_file(".coverage") # If the test fails, it's helpful to see this info: fname = abs_file("try_it.py") linenos = data.lines(fname) print("{0}: {1}".format(len(linenos), linenos)) print_simple_annotation(code, linenos) lines = line_count(code) self.assertEqual(data.line_counts()['try_it.py'], lines) else: expected_out = ( "Can't support concurrency=%s with PyTracer, " "only threads are supported\n" % concurrency ) self.assertEqual(out, expected_out)
def test_branch(self): cov = coverage.Coverage(branch=True) self.make_file("fun1.py", """\ def fun1(x): if x == 1: return fun1(3) """) self.start_import_stop(cov, "fun1") data = cov.get_data() fun1_lines = data.lines(abs_file("fun1.py")) self.assertCountEqual(fun1_lines, [1, 2, 5])
def test_branch(self): cov = coverage.Coverage(branch=True) self.make_file("fun1.py", """\ def fun1(x): if x == 1: return fun1(3) """) self.start_import_stop(cov, "fun1") cov._harvest_data() fun1_lines = cov.data.line_data()[abs_file("fun1.py")] self.assertEqual(fun1_lines, [1, 2, 5])
def test_report_no_extension(self): self.make_file( "xxx", """\ # This is a python file though it doesn't look like it, like a main script. a = b = c = d = 0 a = 3 b = 4 if not b: c = 6 d = 7 print(f"xxx: {a} {b} {c} {d}") """) self.make_data_file(lines={abs_file("xxx"): [2, 3, 4, 5, 7, 8]}) cov = coverage.Coverage() cov.load() report = self.get_report(cov) assert self.last_line_squeezed(report) == "TOTAL 7 1 86%"
def try_some_code(self, code, concurrency, the_module, expected_out=None): """Run some concurrency testing code and see that it was all covered. `code` is the Python code to execute. `concurrency` is the name of the concurrency regime to test it under. `the_module` is the imported module that must be available for this to work at all. `expected_out` is the text we expect the code to produce. """ self.make_file("try_it.py", code) cmd = "coverage run --concurrency=%s try_it.py" % concurrency out = self.run_command(cmd) if not the_module: # We don't even have the underlying module installed, we expect # coverage to alert us to this fact. expected_out = ("Couldn't trace with concurrency=%s, " "the module isn't installed.\n" % concurrency) self.assertEqual(out, expected_out) elif env.C_TRACER or concurrency == "thread": # We can fully measure the code if we are using the C tracer, which # can support all the concurrency, or if we are using threads. if expected_out is None: expected_out = "%d\n" % (sum(range(self.LIMIT))) self.assertEqual(out, expected_out) # Read the coverage file and see that try_it.py has all its lines # executed. data = coverage.CoverageData() data.read_file(".coverage") # If the test fails, it's helpful to see this info: fname = abs_file("try_it.py") linenos = data.lines(fname) print("{0}: {1}".format(len(linenos), linenos)) print_simple_annotation(code, linenos) lines = line_count(code) self.assertEqual(data.line_counts()['try_it.py'], lines) else: expected_out = ("Can't support concurrency=%s with PyTracer, " "only threads are supported\n" % concurrency) self.assertEqual(out, expected_out)
def abs_file_dict(d): """Return a dict like d, but with keys modified by `abs_file`.""" # The call to litems() ensures that the GIL protects the dictionary # iterator against concurrent modifications by tracers running # in other threads. We try three times in case of concurrent # access, hoping to get a clean copy. runtime_err = None for _ in range(3): try: items = litems(d) except RuntimeError as ex: runtime_err = ex else: break else: raise runtime_err # pylint: disable=raising-bad-type return dict((abs_file(k), v) for k, v in items)
def test_moving_stuff(self): # When using absolute file names, moving the source around results in # "No source for code" errors while reporting. self.make_file("foo.py", "a = 1") cov = coverage.Coverage(source=["."]) self.start_import_stop(cov, "foo") res = cov.report() assert res == 100 expected = re.escape("No source for code: '{}'.".format(abs_file("foo.py"))) os.remove("foo.py") self.make_file("new/foo.py", "a = 1") shutil.move(".coverage", "new/.coverage") with change_dir("new"): cov = coverage.Coverage() cov.load() with pytest.raises(CoverageException, match=expected): cov.report()
def test_branch(self): cov = coverage.Coverage(branch=True) self.make_file( "fun1.py", """\ def fun1(x): if x == 1: return fun1(3) """, ) self.start_import_stop(cov, "fun1") data = cov.get_data() fun1_lines = data.lines(abs_file("fun1.py")) self.assertCountEqual(fun1_lines, [1, 2, 5])
def test_assert_source(self): dom = ElementTree.fromstring("""\ <doc> <src>foo</src> <sources> <source>{cwd}something</source> <source>{cwd}another</source> </sources> </doc> """.format(cwd=abs_file(".")+os.sep)) self.assert_source(dom, "something") self.assert_source(dom, "another") with self.assertRaises(AssertionError): self.assert_source(dom, "hello") with self.assertRaises(AssertionError): self.assert_source(dom, "foo") with self.assertRaises(AssertionError): self.assert_source(dom, "thing")
def test_assert_source(self): dom = ElementTree.fromstring("""\ <doc> <src>foo</src> <sources> <source>{cwd}something</source> <source>{cwd}another</source> </sources> </doc> """.format(cwd=abs_file(".") + os.sep)) self.assert_source(dom, "something") self.assert_source(dom, "another") with self.assertRaises(AssertionError): self.assert_source(dom, "hello") with self.assertRaises(AssertionError): self.assert_source(dom, "foo") with self.assertRaises(AssertionError): self.assert_source(dom, "thing")
def try_some_code(self, code, concurrency, the_module, expected_out=None): """Run some concurrency testing code and see that it was all covered. `code` is the Python code to execute. `concurrency` is the name of the concurrency regime to test it under. `the_module` is the imported module that must be available for this to work at all. `expected_out` is the text we expect the code to produce. """ self.make_file("try_it.py", code) cmd = "coverage run --concurrency=%s try_it.py" % concurrency out = self.run_command(cmd) expected_cant_trace = cant_trace_msg(concurrency, the_module) if expected_cant_trace is not None: assert out == expected_cant_trace else: # We can fully measure the code if we are using the C tracer, which # can support all the concurrency, or if we are using threads. if expected_out is None: expected_out = "%d\n" % (sum(range(self.QLIMIT))) print(code) assert out == expected_out # Read the coverage file and see that try_it.py has all its lines # executed. data = coverage.CoverageData(".coverage") data.read() # If the test fails, it's helpful to see this info: fname = abs_file("try_it.py") linenos = data.lines(fname) print(f"{len(linenos)}: {linenos}") print_simple_annotation(code, linenos) lines = line_count(code) assert line_counts(data)['try_it.py'] == lines
def try_some_code(self, code, concurrency, the_module, expected_out=None): """Run some concurrency testing code and see that it was all covered. `code` is the Python code to execute. `concurrency` is the name of the concurrency regime to test it under. `the_module` is the imported module that must be available for this to work at all. `expected_out` is the text we expect the code to produce. """ self.make_file("try_it.py", code) cmd = "coverage run --concurrency=%s try_it.py" % concurrency out = self.run_command(cmd) expected_cant_trace = cant_trace_msg(concurrency, the_module) if expected_cant_trace is not None: self.assertEqual(out, expected_cant_trace) else: # We can fully measure the code if we are using the C tracer, which # can support all the concurrency, or if we are using threads. if expected_out is None: expected_out = "%d\n" % (sum(range(self.QLIMIT))) print(code) self.assertEqual(out, expected_out) # Read the coverage file and see that try_it.py has all its lines # executed. data = coverage.CoverageData(".coverage") data.read() # If the test fails, it's helpful to see this info: fname = abs_file("try_it.py") linenos = data.lines(fname) print("{0}: {1}".format(len(linenos), linenos)) print_simple_annotation(code, linenos) lines = line_count(code) self.assertEqual(line_counts(data)['try_it.py'], lines)
def _get_file_reporter(self, morf): """Get a FileReporter for a module or filename.""" plugin = None if isinstance(morf, string_class): abs_morf = abs_file(morf) plugin_name = self.data.plugin_name(abs_morf) if plugin_name: plugin = self.plugins.get(plugin_name) if plugin: file_reporter = plugin.file_reporter(abs_morf) if file_reporter is None: raise CoverageException( "Plugin %r did not provide a file reporter for %r." % ( plugin._coverage_plugin_name, morf ) ) else: file_reporter = PythonFileReporter(morf, self) return file_reporter
def test_accented_directory(self): # Make a file with a non-ascii character in the directory name. self.make_file("\xe2/accented.py", "print('accented')") self.make_data_file(lines={abs_file("\xe2/accented.py"): [1]}) # The XML report is always UTF8-encoded. cov = coverage.Coverage() cov.load() cov.xml_report() with open("coverage.xml", "rb") as xmlf: xml = xmlf.read() assert b' filename="\xc3\xa2/accented.py"' in xml assert b' name="accented.py"' in xml dom = ElementTree.parse("coverage.xml") elts = dom.findall(".//package[@name='â']") assert len(elts) == 1 assert elts[0].attrib == { "branch-rate": "0", "complexity": "0", "line-rate": "1", "name": "â", }
def abs_file_dict(d): """Return a dict like d, but with keys modified by `abs_file`.""" return dict((abs_file(k), v) for k, v in iitems(d))
def test_exception(self): # Python 2.3's trace function doesn't get called with "return" if the # scope is exiting due to an exception. This confounds our trace # function which relies on scope announcements to track which files to # trace. # # This test is designed to sniff this out. Each function in the call # stack is in a different file, to try to trip up the tracer. Each # file has active lines in a different range so we'll see if the lines # get attributed to the wrong file. self.make_file("oops.py", """\ def oops(args): a = 2 raise Exception("oops") a = 4 """) self.make_file("fly.py", "\n"*100 + """\ def fly(calls): a = 2 calls[0](calls[1:]) a = 4 """) self.make_file("catch.py", "\n"*200 + """\ def catch(calls): try: a = 3 calls[0](calls[1:]) a = 5 except: a = 7 """) self.make_file("doit.py", "\n"*300 + """\ def doit(calls): try: calls[0](calls[1:]) except: a = 5 """) # Import all the modules before starting coverage, so the def lines # won't be in all the results. for mod in "oops fly catch doit".split(): import_local_file(mod) # Each run nests the functions differently to get different # combinations of catching exceptions and letting them fly. runs = [ ("doit fly oops", { 'doit.py': [302, 303, 304, 305], 'fly.py': [102, 103], 'oops.py': [2, 3], }), ("doit catch oops", { 'doit.py': [302, 303], 'catch.py': [202, 203, 204, 206, 207], 'oops.py': [2, 3], }), ("doit fly catch oops", { 'doit.py': [302, 303], 'fly.py': [102, 103, 104], 'catch.py': [202, 203, 204, 206, 207], 'oops.py': [2, 3], }), ("doit catch fly oops", { 'doit.py': [302, 303], 'catch.py': [202, 203, 204, 206, 207], 'fly.py': [102, 103], 'oops.py': [2, 3], }), ] for callnames, lines_expected in runs: # Make the list of functions we'll call for this test. callnames = callnames.split() calls = [getattr(sys.modules[cn], cn) for cn in callnames] cov = coverage.Coverage() cov.start() # Call our list of functions: invoke the first, with the rest as # an argument. calls[0](calls[1:]) # pragma: nested cov.stop() # pragma: nested # Clean the line data and compare to expected results. # The file names are absolute, so keep just the base. clean_lines = {} data = cov.get_data() for callname in callnames: filename = callname + ".py" lines = data.lines(abs_file(filename)) clean_lines[filename] = sorted(lines) if env.JYTHON: # pragma: only jython # Jython doesn't report on try or except lines, so take those # out of the expected lines. invisible = [202, 206, 302, 304] for lines in lines_expected.values(): lines[:] = [l for l in lines if l not in invisible] self.assertEqual(clean_lines, lines_expected)
def assert_source(self, xmldom, src): """Assert that the XML has a <source> element with `src`.""" src = abs_file(src) elts = xmldom.findall(".//sources/source") assert any(elt.text == src for elt in elts)
def assert_source(self, xml, src): """Assert that the XML has a <source> element with `src`.""" src = abs_file(src) self.assertRegex(xml, r'<source>\s*{0}\s*</source>'.format(re.escape(src)))
def make_mycode_data(self): """Pretend that we ran mycode.py, so we can report on it.""" self.make_file("mycode.py", "print('hello')\n") self.make_data_file(lines={abs_file("mycode.py"): [1]})
def abs_path(self, p): """Return the absolute path for `p`.""" return os.path.join(abs_file(os.getcwd()), os.path.normpath(p))