diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index 35cf938c7..726f686ff 100644 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -148,16 +148,16 @@ def file_replace(file_name: Path, old_text: str, new_text: str) -> Iterator[None """ file_text = "" if old_text: - file_text = file_name.read_text() + file_text = file_name.read_text(encoding="utf-8") if old_text not in file_text: raise Exception("Old text {old_text!r} not found in {file_name}") updated_text = file_text.replace(old_text, new_text) - file_name.write_text(updated_text) + file_name.write_text(updated_text, encoding="utf-8") try: yield finally: if old_text: - file_name.write_text(file_text) + file_name.write_text(file_text, encoding="utf-8") def file_must_exist(file_name: str, kind: str = "file") -> Path: @@ -624,7 +624,7 @@ def run_no_coverage(self, env: Env) -> float: def run_with_coverage(self, env: Env, cov_ver: Coverage) -> float: env.shell.run_command(f"{env.python} -m pip install {cov_ver.pip_args}") pforce = Path("force.ini") - pforce.write_text("[run]\nbranch=false\n") + pforce.write_text("[run]\nbranch=false\n", encoding="utf-8") with env.shell.set_env({"COVERAGE_FORCE_CONFIG": str(pforce.resolve())}): env.shell.run_command(f"{env.python} -m pytest {self.FAST} --cov") duration = env.shell.last_duration @@ -907,13 +907,13 @@ def __init__( def save_results(self) -> None: """Save current results to the JSON file.""" - with self.results_file.open("w") as f: + with self.results_file.open("w", encoding="utf-8") as f: json.dump({" ".join(k): v for k, v in self.result_data.items()}, f) def load_results(self) -> dict[ResultKey, list[float]]: """Load results from the JSON file if it exists.""" if self.results_file.exists(): - with self.results_file.open("r") as f: + with self.results_file.open("r", encoding="utf-8") as f: data: dict[str, list[float]] = json.load(f) return { (k.split()[0], k.split()[1], k.split()[2]): v for k, v in data.items() diff --git a/coverage/html.py b/coverage/html.py index 2cc68ac1d..55dd32e2e 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -51,7 +51,7 @@ def data_filename(fname: str) -> str: def read_data(fname: str) -> str: """Return the contents of a data file of ours.""" - with open(data_filename(fname)) as data_file: + with open(data_filename(fname), encoding="utf-8") as data_file: return data_file.read() @@ -412,7 +412,7 @@ def make_local_static_report_files(self) -> None: # .gitignore can't be copied from the source tree because if it was in # the source tree, it would stop the static files from being checked in. if self.directory_was_empty: - with open(os.path.join(self.directory, ".gitignore"), "w") as fgi: + with open(os.path.join(self.directory, ".gitignore"), "w", encoding="utf-8") as fgi: fgi.write("# Created by coverage.py\n*\n") def should_report(self, analysis: Analysis, index_page: IndexPage) -> bool: @@ -706,7 +706,7 @@ def read(self) -> None: """Read the information we stored last time.""" try: status_file = os.path.join(self.directory, self.STATUS_FILE) - with open(status_file) as fstatus: + with open(status_file, encoding="utf-8") as fstatus: status = json.load(fstatus) except (OSError, ValueError): # Status file is missing or malformed. @@ -747,7 +747,7 @@ def write(self) -> None: for fname, finfo in self.files.items() }, } - with open(status_file, "w") as fout: + with open(status_file, "w", encoding="utf-8") as fout: json.dump(status_data, fout, separators=(",", ":")) def check_global_data(self, *data: Any) -> None: diff --git a/coverage/pytracer.py b/coverage/pytracer.py index eae848d28..31b4866cb 100644 --- a/coverage/pytracer.py +++ b/coverage/pytracer.py @@ -126,7 +126,7 @@ def __repr__(self) -> str: def log(self, marker: str, *args: Any) -> None: """For hard-core logging of what this tracer is doing.""" - with open("/tmp/debug_trace.txt", "a") as f: + with open("/tmp/debug_trace.txt", "a", encoding="utf-8") as f: f.write(f"{marker} {self.id}[{len(self.data_stack)}]") if 0: # if you want thread ids.. f.write(".{:x}.{:x}".format( # type: ignore[unreachable] diff --git a/coverage/sysmon.py b/coverage/sysmon.py index 8e5376cf0..b6b0d9060 100644 --- a/coverage/sysmon.py +++ b/coverage/sysmon.py @@ -104,7 +104,7 @@ def log(msg: str) -> None: # f"{root}-{pid}.out", # f"{root}-{pid}-{tslug}.out", ]: - with open(filename, "a") as f: + with open(filename, "a", encoding="utf-8") as f: try: print(f"{pid}:{tslug}: {msg}", file=f, flush=True) except UnicodeError: diff --git a/doc/cog_helpers.py b/doc/cog_helpers.py index d30030875..70161ab0d 100644 --- a/doc/cog_helpers.py +++ b/doc/cog_helpers.py @@ -52,7 +52,7 @@ def _read_config(text, fname): text = textwrap.dedent(text[1:]) os.makedirs("tmp", exist_ok=True) - with open(f"tmp/{fname}", "w") as f: + with open(f"tmp/{fname}", "w", encoding="utf-8") as f: f.write(text) config = read_coverage_config(f"tmp/{fname}", warn=cog.error) diff --git a/doc/conf.py b/doc/conf.py index 57a1ffd00..7b7040054 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -215,7 +215,7 @@ # missing, so only use the extension if we are specifically spell-checking. extensions += ['sphinxcontrib.spelling'] names_file = tempfile.NamedTemporaryFile(mode='w', prefix="coverage_names_", suffix=".txt") - with open("../CONTRIBUTORS.txt") as contributors: + with open("../CONTRIBUTORS.txt", encoding="utf-8") as contributors: names = set(re.split(r"[^\w']", contributors.read())) names = [n for n in names if len(n) >= 2 and n[0].isupper()] names_file.write("\n".join(names)) diff --git a/igor.py b/igor.py index 83c9dae76..9f7b23e12 100644 --- a/igor.py +++ b/igor.py @@ -196,7 +196,7 @@ def run_tests_with_coverage(core, *runner_args): # There's an entry in "make clean" to get rid of this file. pth_dir = sysconfig.get_path("purelib") pth_path = os.path.join(pth_dir, "zzz_metacov.pth") - with open(pth_path, "w") as pth_file: + with open(pth_path, "w", encoding="utf-8") as pth_file: pth_file.write("import coverage; coverage.process_startup()\n") suffix = f"{make_env_id(core)}_{platform.platform()}" @@ -374,14 +374,14 @@ def get_release_facts(): def update_file(fname, pattern, replacement): """Update the contents of a file, replacing pattern with replacement.""" - with open(fname) as fobj: + with open(fname, encoding="utf-8") as fobj: old_text = fobj.read() new_text = re.sub(pattern, replacement, old_text, count=1) if new_text != old_text: print(f"Updating {fname}") - with open(fname, "w") as fobj: + with open(fname, "w", encoding="utf-8") as fobj: fobj.write(new_text) diff --git a/lab/branch_trace.py b/lab/branch_trace.py index 7e8e88f9a..c2623c477 100644 --- a/lab/branch_trace.py +++ b/lab/branch_trace.py @@ -11,7 +11,7 @@ def trace(frame, event, arg): last = this return trace -code = open(sys.argv[1]).read() +code = open(sys.argv[1], encoding="utf-8").read() sys.settrace(trace) exec(code) print(sorted(pairs)) diff --git a/lab/extract_code.py b/lab/extract_code.py index 3940a2042..cf32c1730 100644 --- a/lab/extract_code.py +++ b/lab/extract_code.py @@ -52,7 +52,7 @@ def f(a, b): fname, lineno = sys.argv[1:] lineno = int(lineno) -with open(fname) as code_file: +with open(fname, encoding="utf-8") as code_file: lines = ["", *code_file] # Find opening triple-quote diff --git a/lab/goals.py b/lab/goals.py index 4bda0f0f5..13f3f68a5 100644 --- a/lab/goals.py +++ b/lab/goals.py @@ -64,7 +64,7 @@ def main(argv): print("Need either --file or --group") return 1 - with open("coverage.json") as j: + with open("coverage.json", encoding="utf-8") as j: data = json.load(j) all_files = list(data["files"].keys()) selected = select_files(all_files, args.pattern) diff --git a/lab/run_sysmon.py b/lab/run_sysmon.py index f88988bbe..fa7d44d7b 100644 --- a/lab/run_sysmon.py +++ b/lab/run_sysmon.py @@ -9,7 +9,7 @@ print(sys.version) the_program = sys.argv[1] -code = compile(open(the_program).read(), filename=the_program, mode="exec") +code = compile(open(the_program, encoding="utf-8").read(), filename=the_program, mode="exec") my_id = sys.monitoring.COVERAGE_ID sys.monitoring.use_tool_id(my_id, "run_sysmon.py") diff --git a/lab/run_trace.py b/lab/run_trace.py index 54e6a53f0..7e8354877 100644 --- a/lab/run_trace.py +++ b/lab/run_trace.py @@ -32,6 +32,6 @@ def trace(frame, event, arg): print(sys.version) the_program = sys.argv[1] -code = open(the_program).read() +code = open(the_program, encoding="utf-8").read() sys.settrace(trace) exec(code) diff --git a/lab/show_pyc.py b/lab/show_pyc.py index 0585b937f..a044eca2c 100644 --- a/lab/show_pyc.py +++ b/lab/show_pyc.py @@ -44,7 +44,7 @@ def show_pyc_file(fname): show_code(code) def show_py_file(fname): - text = open(fname).read().replace('\r\n', '\n') + text = open(fname, encoding="utf-8").read().replace('\r\n', '\n') show_py_text(text, fname=fname) def show_py_text(text, fname=""): diff --git a/setup.py b/setup.py index 8fcec5e2d..92fad6502 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ """ cov_ver_py = os.path.join(os.path.split(__file__)[0], "coverage/version.py") -with open(cov_ver_py) as version_file: +with open(cov_ver_py, encoding="utf-8") as version_file: # __doc__ will be overwritten by version.py. doc = __doc__ # Keep pylint happy. @@ -43,7 +43,7 @@ # Execute the code in version.py. exec(compile(version_file.read(), cov_ver_py, "exec", dont_inherit=True)) -with open("README.rst") as readme: +with open("README.rst", encoding="utf-8") as readme: readme_text = readme.read() temp_url = __url__.replace("readthedocs", "@@") diff --git a/tests/balance_xdist_plugin.py b/tests/balance_xdist_plugin.py index 64a8c85f1..114d4ad3b 100644 --- a/tests/balance_xdist_plugin.py +++ b/tests/balance_xdist_plugin.py @@ -71,7 +71,7 @@ def pytest_sessionstart(self, session): if self.worker == "none": if tests_csv_dir.exists(): for csv_file in tests_csv_dir.iterdir(): - with csv_file.open(newline="") as fcsv: + with csv_file.open(newline="", encoding="utf-8") as fcsv: reader = csv.reader(fcsv) for row in reader: self.times[row[1]] += float(row[3]) @@ -81,7 +81,7 @@ def write_duration_row(self, item, phase, duration): """Helper to write a row to the tracked-test csv file.""" if self.running_all: self.tests_csv.parent.mkdir(parents=True, exist_ok=True) - with self.tests_csv.open("a", newline="") as fcsv: + with self.tests_csv.open("a", newline="", encoding="utf-8") as fcsv: csv.writer(fcsv).writerow([self.worker, item.nodeid, phase, duration]) @pytest.hookimpl(hookwrapper=True) @@ -171,7 +171,7 @@ def show_worker_times(): # pragma: debugging tests_csv_dir = Path("tmp/tests_csv") for csv_file in tests_csv_dir.iterdir(): - with csv_file.open(newline="") as fcsv: + with csv_file.open(newline="", encoding="utf-8") as fcsv: reader = csv.reader(fcsv) for row in reader: worker = row[0] diff --git a/tests/conftest.py b/tests/conftest.py index 9099801d8..9cd953bbd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -104,7 +104,8 @@ def pytest_sessionstart() -> None: # Create a .pth file for measuring subprocess coverage. pth_dir = find_writable_pth_directory() assert pth_dir - (pth_dir / "subcover.pth").write_text("import coverage; coverage.process_startup()\n") + sub_dir = pth_dir / "subcover.pth" + sub_dir.write_text("import coverage; coverage.process_startup()\n", encoding="utf-8") # subcover.pth is deleted by pytest_sessionfinish below. @@ -137,7 +138,7 @@ def find_writable_pth_directory() -> Path | None: for pth_dir in possible_pth_dirs(): # pragma: part covered try_it = pth_dir / f"touch_{WORKER}.it" try: - try_it.write_text("foo") + try_it.write_text("foo", encoding="utf-8") except OSError: # pragma: cant happen continue diff --git a/tests/goldtest.py b/tests/goldtest.py index 7ca4af159..4ff8276f2 100644 --- a/tests/goldtest.py +++ b/tests/goldtest.py @@ -59,8 +59,8 @@ def save_mismatch(f: str) -> None: save_path = expected_dir.replace(os_sep("/gold/"), os_sep("/actual/")) os.makedirs(save_path, exist_ok=True) save_file = os.path.join(save_path, f) - with open(save_file, "w") as savef: - with open(os.path.join(actual_dir, f)) as readf: + with open(save_file, "w", encoding="utf-8") as savef: + with open(os.path.join(actual_dir, f), encoding="utf-8") as readf: savef.write(readf.read()) print(os_sep(f"Saved actual output to '{save_file}': see tests/gold/README.rst")) @@ -70,13 +70,13 @@ def save_mismatch(f: str) -> None: text_diff = [] for f in diff_files: expected_file = os.path.join(expected_dir, f) - with open(expected_file) as fobj: + with open(expected_file, encoding="utf-8") as fobj: expected = fobj.read() if expected_file.endswith(".xml"): expected = canonicalize_xml(expected) actual_file = os.path.join(actual_dir, f) - with open(actual_file) as fobj: + with open(actual_file, encoding="utf-8") as fobj: actual = fobj.read() if actual_file.endswith(".xml"): actual = canonicalize_xml(actual) @@ -114,7 +114,7 @@ def contains(filename: str, *strlist: str) -> None: """ __tracebackhide__ = True # pytest, please don't show me this function. - with open(filename) as fobj: + with open(filename, encoding="utf-8") as fobj: text = fobj.read() for s in strlist: assert s in text, f"Missing content in {filename}: {s!r}" @@ -128,7 +128,7 @@ def contains_rx(filename: str, *rxlist: str) -> None: """ __tracebackhide__ = True # pytest, please don't show me this function. - with open(filename) as fobj: + with open(filename, encoding="utf-8") as fobj: lines = fobj.readlines() for rx in rxlist: assert any(re.search(rx, line) for line in lines), ( @@ -144,7 +144,7 @@ def contains_any(filename: str, *strlist: str) -> None: """ __tracebackhide__ = True # pytest, please don't show me this function. - with open(filename) as fobj: + with open(filename, encoding="utf-8") as fobj: text = fobj.read() for s in strlist: if s in text: @@ -161,7 +161,7 @@ def doesnt_contain(filename: str, *strlist: str) -> None: """ __tracebackhide__ = True # pytest, please don't show me this function. - with open(filename) as fobj: + with open(filename, encoding="utf-8") as fobj: text = fobj.read() for s in strlist: assert s not in text, f"Forbidden content in {filename}: {s!r}" diff --git a/tests/helpers.py b/tests/helpers.py index 637232ab2..b3ab529cf 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -15,6 +15,7 @@ import re import shutil import subprocess +import sys import textwrap import warnings @@ -42,10 +43,18 @@ def run_command(cmd: str) -> tuple[int, str]: # Subprocesses are expensive, but convenient, and so may be over-used in # the test suite. Use these lines to get a list of the tests using them: if 0: # pragma: debugging - with open("/tmp/processes.txt", "a") as proctxt: # type: ignore[unreachable] + pth = "/tmp/processes.txt" # type: ignore[unreachable] + with open(pth, "a", encoding="utf-8") as proctxt: print(os.getenv("PYTEST_CURRENT_TEST", "unknown"), file=proctxt, flush=True) - encoding = os.device_encoding(1) or locale.getpreferredencoding() + # Type checking trick due to "unreachable" being set + _locale_type_erased: Any = locale + + encoding = os.device_encoding(1) or ( + _locale_type_erased.getpreferredencoding() + if sys.version_info < (3, 11) + else _locale_type_erased.getencoding() + ) # In some strange cases (PyPy3 in a virtualenv!?) the stdout encoding of # the subprocess is set incorrectly to ascii. Use an environment variable @@ -113,7 +122,7 @@ def make_file( if text and basename.endswith(".py") and SHOW_DIS: # pragma: debugging os.makedirs("/tmp/dis", exist_ok=True) - with open(f"/tmp/dis/{basename}.dis", "w") as fdis: + with open(f"/tmp/dis/{basename}.dis", "w", encoding="utf-8") as fdis: print(f"# {os.path.abspath(filename)}", file=fdis) cur_test = os.getenv("PYTEST_CURRENT_TEST", "unknown") print(f"# PYTEST_CURRENT_TEST = {cur_test}", file=fdis) diff --git a/tests/osinfo.py b/tests/osinfo.py index f55fe88c1..e90d5dcf2 100644 --- a/tests/osinfo.py +++ b/tests/osinfo.py @@ -64,7 +64,7 @@ def _VmB(key: str) -> int: """Read the /proc/PID/status file to find memory use.""" try: # Get pseudo file /proc//status - with open(f"/proc/{os.getpid()}/status") as t: + with open(f"/proc/{os.getpid()}/status", encoding="utf-8") as t: v = t.read() except OSError: # pragma: cant happen return 0 # non-Linux? diff --git a/tests/test_arcs.py b/tests/test_arcs.py index 7ec0e663a..966f80241 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -188,7 +188,7 @@ class WithTest(CoverageTest): def test_with(self) -> None: self.check_coverage("""\ def example(): - with open("test", "w") as f: + with open("test", "w", encoding="utf-8") as f: f.write("3") a = 4 @@ -201,7 +201,7 @@ def example(): def test_with_return(self) -> None: self.check_coverage("""\ def example(): - with open("test", "w") as f: + with open("test", "w", encoding="utf-8") as f: f.write("3") return 4 @@ -215,7 +215,7 @@ def test_bug_146(self) -> None: # https://github.com/nedbat/coveragepy/issues/146 self.check_coverage("""\ for i in range(2): - with open("test", "w") as f: + with open("test", "w", encoding="utf-8") as f: print(3) print(4) print(5) @@ -228,9 +228,9 @@ def test_bug_146(self) -> None: def test_nested_with_return(self) -> None: self.check_coverage("""\ def example(x): - with open("test", "w") as f2: + with open("test", "w", encoding="utf-8") as f2: a = 3 - with open("test2", "w") as f4: + with open("test2", "w", encoding="utf-8") as f4: f2.write("5") return 6 @@ -243,7 +243,7 @@ def example(x): def test_break_through_with(self) -> None: self.check_coverage("""\ for i in range(1+1): - with open("test", "w") as f: + with open("test", "w", encoding="utf-8") as f: print(3) break print(5) @@ -255,7 +255,7 @@ def test_break_through_with(self) -> None: def test_continue_through_with(self) -> None: self.check_coverage("""\ for i in range(1+1): - with open("test", "w") as f: + with open("test", "w", encoding="utf-8") as f: print(3) continue print(5) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 98f72f77a..c9c25d934 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -653,7 +653,7 @@ def test_thread_safe_save_data(tmp_path: pathlib.Path) -> None: modules_dir.mkdir() module_names = [f"m{i:03d}" for i in range(1000)] for module_name in module_names: - (modules_dir / (module_name + ".py")).write_text("def f(): pass\n") + (modules_dir / (module_name + ".py")).write_text("def f(): pass\n", encoding="utf-8") # Shared variables for threads should_run = [True] diff --git a/tests/test_debug.py b/tests/test_debug.py index 163f9423f..d1ab6f57c 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -268,14 +268,14 @@ def test_envvar(self) -> None: self.set_environ("COVERAGE_DEBUG_FILE", "debug.out") self.debug_sys() assert ("", "") == self.stdouterr() - with open("debug.out") as f: + with open("debug.out", encoding="utf-8") as f: assert_good_debug_sys(f.read()) def test_config_file(self) -> None: self.make_file(".coveragerc", "[run]\ndebug_file = lotsa_info.txt") self.debug_sys() assert ("", "") == self.stdouterr() - with open("lotsa_info.txt") as f: + with open("lotsa_info.txt", encoding="utf-8") as f: assert_good_debug_sys(f.read()) def test_stdout_alias(self) -> None: diff --git a/tests/test_execfile.py b/tests/test_execfile.py index cd12dea99..930a31a0d 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -89,7 +89,7 @@ def test_missing_final_newline(self) -> None: a = 1 print(f"a is {a!r}") #""") - with open("abrupt.py") as f: + with open("abrupt.py", encoding="utf-8") as f: abrupt = f.read() assert abrupt[-1] == '#' run_python_file(["abrupt.py"]) diff --git a/tests/test_goldtest.py b/tests/test_goldtest.py index f4972ab1c..113ecc8c6 100644 --- a/tests/test_goldtest.py +++ b/tests/test_goldtest.py @@ -80,7 +80,7 @@ def test_bad(self) -> None: assert " D/D/D, Gxxx, Pennsylvania" in stdout # The actual file was saved. - with open(ACTUAL_GETTY_FILE) as f: + with open(ACTUAL_GETTY_FILE, encoding="utf-8") as f: saved = f.read() assert saved == BAD_GETTY diff --git a/tests/test_html.py b/tests/test_html.py index 67b8933aa..627690bd7 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -72,7 +72,7 @@ def get_html_report_content(self, module: str) -> str: """Return the content of the HTML report for `module`.""" filename = flat_rootname(module) + ".html" filename = os.path.join("htmlcov", filename) - with open(filename) as f: + with open(filename, encoding="utf-8") as f: return f.read() def get_html_index_content(self) -> str: @@ -81,7 +81,7 @@ def get_html_index_content(self) -> str: Time stamps are replaced with a placeholder so that clocks don't matter. """ - with open("htmlcov/index.html") as f: + with open("htmlcov/index.html", encoding="utf-8") as f: index = f.read() index = re.sub( r"created at \d{4}-\d{2}-\d{2} \d{2}:\d{2} \+\d{4}", @@ -122,7 +122,7 @@ def assert_valid_hrefs(self, directory: str = "htmlcov") -> None: """ hrefs = collections.defaultdict(set) for fname in glob.glob(f"{directory}/*.html"): - with open(fname) as fhtml: + with open(fname, encoding="utf-8") as fhtml: html = fhtml.read() for href in re.findall(r""" href=['"]([^'"]*)['"]""", html): if href.startswith("#"): @@ -182,11 +182,11 @@ class FileWriteTracker: def __init__(self, written: set[str]) -> None: self.written = written - def open(self, filename: str, mode: str = "r") -> IO[str]: + def open(self, filename: str, mode: str = "r", encoding: str | None = None) -> IO[str]: """Be just like `open`, but write written file names to `self.written`.""" if mode.startswith("w"): self.written.add(filename.replace('\\', '/')) - return open(filename, mode) + return open(filename, mode, encoding=encoding) class HtmlDeltaTest(HtmlTestHelpers, CoverageTest): @@ -361,12 +361,12 @@ def test_status_format_change(self) -> None: self.create_initial_files() self.run_coverage() - with open("htmlcov/status.json") as status_json: + with open("htmlcov/status.json", encoding="utf-8") as status_json: status_data = json.load(status_json) assert status_data['format'] == 5 status_data['format'] = 99 - with open("htmlcov/status.json", "w") as status_json: + with open("htmlcov/status.json", "w", encoding="utf-8") as status_json: json.dump(status_data, status_json) self.run_coverage() @@ -382,7 +382,7 @@ def test_dont_overwrite_gitignore(self) -> None: self.create_initial_files() self.make_file("htmlcov/.gitignore", "# ignore nothing") self.run_coverage() - with open("htmlcov/.gitignore") as fgi: + with open("htmlcov/.gitignore", encoding="utf-8") as fgi: assert fgi.read() == "# ignore nothing" def test_dont_write_gitignore_into_existing_directory(self) -> None: @@ -609,9 +609,9 @@ def test_has_date_stamp_in_files(self) -> None: self.create_initial_files() self.run_coverage() - with open("htmlcov/index.html") as f: + with open("htmlcov/index.html", encoding="utf-8") as f: self.assert_correct_timestamp(f.read()) - with open("htmlcov/main_file_py.html") as f: + with open("htmlcov/main_file_py.html", encoding="utf-8") as f: self.assert_correct_timestamp(f.read()) def test_reporting_on_unmeasured_file(self) -> None: @@ -1259,7 +1259,7 @@ def test_accented_dot_py(self) -> None: cov.load() cov.html_report() self.assert_exists("htmlcov/h\xe2t_py.html") - with open("htmlcov/index.html") as indexf: + with open("htmlcov/index.html", encoding="utf-8") as indexf: index = indexf.read() assert 'hât.py' in index @@ -1273,7 +1273,7 @@ def test_accented_directory(self) -> None: cov.load() cov.html_report() self.assert_exists("htmlcov/z_5786906b6f0ffeb4_accented_py.html") - with open("htmlcov/index.html") as indexf: + with open("htmlcov/index.html", encoding="utf-8") as indexf: index = indexf.read() expected = 'â%saccented.py' assert expected % os.sep in index diff --git a/tests/test_json.py b/tests/test_json.py index e5b116ac4..aeeb81c35 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -83,7 +83,7 @@ def _compare_json_reports( mod = self.start_import_stop(cov, mod_name) output_path = os.path.join(self.temp_dir, f"{mod_name}.json") cov.json_report(mod, outfile=output_path) - with open(output_path) as result_file: + with open(output_path, encoding="utf-8") as result_file: parsed_result = json.load(result_file) self.assert_recent_datetime( datetime.strptime(parsed_result['meta']['timestamp'], "%Y-%m-%dT%H:%M:%S.%f"), diff --git a/tests/test_lcov.py b/tests/test_lcov.py index 76e99e91d..f4dff3801 100644 --- a/tests/test_lcov.py +++ b/tests/test_lcov.py @@ -43,7 +43,7 @@ def test_volume(self): def get_lcov_report_content(self, filename: str = "coverage.lcov") -> str: """Return the content of an LCOV report.""" - with open(filename) as file: + with open(filename, encoding="utf-8") as file: return file.read() def test_lone_file(self) -> None: diff --git a/tests/test_oddball.py b/tests/test_oddball.py index 7d3199fb5..b253b8927 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -599,7 +599,7 @@ def test_correct_filename(self) -> None: """) self.make_file("main.py", """\ namespace = {'var': 17} - with open("to_exec.py") as to_exec_py: + with open("to_exec.py", encoding="utf-8") as to_exec_py: code = compile(to_exec_py.read(), 'to_exec.py', 'exec') exec(code, globals(), namespace) \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n diff --git a/tests/test_parser.py b/tests/test_parser.py index 8c9f2e7ad..1e9a5db72 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1183,7 +1183,7 @@ def test_missing_line_ending(self) -> None: stderr=subprocess.PIPE).communicate()""") # no final newline. # Double-check that some test helper wasn't being helpful. - with open("abrupt.py") as f: + with open("abrupt.py", encoding="utf-8") as f: assert f.read()[-1] == ")" parser = self.parse_file("abrupt.py") diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py index 0a863ab6e..49d8d25a7 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -128,7 +128,7 @@ def test_stress(self, fname: str) -> None: stress = os.path.join(TESTS_DIR, fname) self.check_file_tokenization(stress) - with open(stress) as fstress: + with open(stress, encoding="utf-8") as fstress: assert re.search(r"(?m) $", fstress.read()), f"{stress} needs a trailing space." diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 44b139e57..a94104a01 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -171,7 +171,7 @@ class Plugin(CoveragePlugin): pass def coverage_init(reg, options): reg.add_noop(Plugin()) - with open("evidence.out", "w") as f: + with open("evidence.out", "w", encoding="utf-8") as f: f.write("we are here!") """) @@ -181,7 +181,7 @@ def coverage_init(reg, options): cov.start() cov.stop() # pragma: nested - with open("evidence.out") as f: + with open("evidence.out", encoding="utf-8") as f: assert f.read() == "we are here!" def test_missing_plugin_raises_import_error(self) -> None: diff --git a/tests/test_process.py b/tests/test_process.py index 2466de081..d2c95bbda 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -410,7 +410,7 @@ def test_fork(self) -> None: data.read() assert line_counts(data)["fork.py"] == total_lines - debug_text = Path("debug.out").read_text() + debug_text = Path("debug.out").read_text(encoding="utf-8") ppid = pids["parent"] cpid = pids["child"] assert ppid != cpid @@ -670,14 +670,14 @@ def assert_tryexecfile_output(self, expected: str, actual: str) -> None: assert actual == expected def test_coverage_run_is_like_python(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("run_me.py", f.read()) expected = self.run_command("python run_me.py") actual = self.run_command("coverage run run_me.py") self.assert_tryexecfile_output(expected, actual) def test_coverage_run_far_away_is_like_python(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("sub/overthere/prog.py", f.read()) expected = self.run_command("python sub/overthere/prog.py") actual = self.run_command("coverage run sub/overthere/prog.py") @@ -685,7 +685,7 @@ def test_coverage_run_far_away_is_like_python(self) -> None: @pytest.mark.skipif(not env.WINDOWS, reason="This is about Windows paths") def test_coverage_run_far_away_is_like_python_windows(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("sub/overthere/prog.py", f.read()) expected = self.run_command("python sub\\overthere\\prog.py") actual = self.run_command("coverage run sub\\overthere\\prog.py") @@ -698,7 +698,7 @@ def test_coverage_run_dashm_is_like_python_dashm(self) -> None: self.assert_tryexecfile_output(expected, actual) def test_coverage_run_dir_is_like_python_dir(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("with_main/__main__.py", f.read()) expected = self.run_command("python with_main") @@ -706,7 +706,7 @@ def test_coverage_run_dir_is_like_python_dir(self) -> None: self.assert_tryexecfile_output(expected, actual) def test_coverage_run_dashm_dir_no_init_is_like_python(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("with_main/__main__.py", f.read()) expected = self.run_command("python -m with_main") @@ -714,7 +714,7 @@ def test_coverage_run_dashm_dir_no_init_is_like_python(self) -> None: self.assert_tryexecfile_output(expected, actual) def test_coverage_run_dashm_dir_with_init_is_like_python(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("with_main/__main__.py", f.read()) self.make_file("with_main/__init__.py", "") @@ -782,7 +782,7 @@ def test_coverage_run_script_imports_doubledashsource(self) -> None: def test_coverage_run_dashm_is_like_python_dashm_off_path(self) -> None: # https://github.com/nedbat/coveragepy/issues/242 self.make_file("sub/__init__.py", "") - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("sub/run_me.py", f.read()) expected = self.run_command("python -m sub.run_me") @@ -801,7 +801,7 @@ def test_coverage_zip_is_like_python(self) -> None: # Test running coverage from a zip file itself. Some environments # (windows?) zip up the coverage main to be used as the coverage # command. - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("run_me.py", f.read()) expected = self.run_command("python run_me.py") cov_main = os.path.join(TESTS_DIR, "covmain.zip") @@ -814,7 +814,7 @@ def test_coverage_zip_is_like_python(self) -> None: reason="Windows gets this wrong: https://github.com/python/cpython/issues/131484", ) def test_pythonsafepath(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("run_me.py", f.read()) self.set_environ("PYTHONSAFEPATH", "1") expected = self.run_command("python run_me.py") @@ -823,7 +823,7 @@ def test_pythonsafepath(self) -> None: @pytest.mark.skipif(env.PYVERSION < (3, 11), reason="PYTHONSAFEPATH is new in 3.11") def test_pythonsafepath_dashm_runme(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("run_me.py", f.read()) self.set_environ("PYTHONSAFEPATH", "1") expected = self.run_command("python run_me.py") @@ -832,7 +832,7 @@ def test_pythonsafepath_dashm_runme(self) -> None: @pytest.mark.skipif(env.PYVERSION < (3, 11), reason="PYTHONSAFEPATH is new in 3.11") def test_pythonsafepath_dashm(self) -> None: - with open(TRY_EXECFILE) as f: + with open(TRY_EXECFILE, encoding="utf-8") as f: self.make_file("with_main/__main__.py", f.read()) self.set_environ("PYTHONSAFEPATH", "1") @@ -1256,7 +1256,7 @@ def setUp(self) -> None: """) # sub.py will write a few lines. self.make_file("sub.py", """\ - f = open("out.txt", "w") + f = open("out.txt", "w", encoding="utf-8") f.write("Hello, world!\\n") f.close() """) @@ -1276,7 +1276,7 @@ def test_subprocess_with_pth_files(self) -> None: self.set_environ("COVERAGE_PROCESS_START", "coverage.ini") import main # pylint: disable=unused-import, import-error - with open("out.txt") as f: + with open("out.txt", encoding="utf-8") as f: assert f.read() == "Hello, world!\n" # Read the data from .coverage @@ -1295,7 +1295,7 @@ def test_subprocess_with_pth_files_and_parallel(self) -> None: self.set_environ("COVERAGE_PROCESS_START", "coverage.ini") self.run_command("coverage run main.py") - with open("out.txt") as f: + with open("out.txt", encoding="utf-8") as f: assert f.read() == "Hello, world!\n" self.run_command("coverage combine") @@ -1368,7 +1368,7 @@ def path(basename: str) -> str: self.make_file(path("__init__.py"), "") # sub.py will write a few lines. self.make_file(path("sub.py"), """\ - f = open("out.txt", "w") + f = open("out.txt", "w", encoding="utf-8") f.write("Hello, world!") f.close() """) @@ -1386,7 +1386,7 @@ def path(basename: str) -> str: self.run_command(cmd) - with open("out.txt") as f: + with open("out.txt", encoding="utf-8") as f: assert f.read() == "Hello, world!" # Read the data from .coverage diff --git a/tests/test_python.py b/tests/test_python.py index 6a8362919..7c8666ac5 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -57,11 +57,11 @@ def test_source_for_file_windows(tmp_path: pathlib.Path) -> None: # On windows if a pyw exists, it is an acceptable source path_windows = tmp_path / "a.pyw" - path_windows.write_text("") + path_windows.write_text("", encoding="utf-8") assert str(path_windows) == source_for_file(src + 'c') # If both pyw and py exist, py is preferred - a_py.write_text("") + a_py.write_text("", encoding="utf-8") assert source_for_file(src + 'c') == src @@ -81,6 +81,6 @@ def test_runpy_path(self, convert_to: str) -> None: import runpy from pathlib import Path pyfile = Path('script.py') - pyfile.write_text('') + pyfile.write_text('', encoding='utf-8') runpy.run_path({convert_to}(pyfile)) """) diff --git a/tests/test_report.py b/tests/test_report.py index fca027f9b..06d095e81 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -787,7 +787,7 @@ def test_report_with_chdir(self) -> None: print("Line One") os.chdir("subdir") print("Line Two") - print(open("something").read()) + print(open("something", encoding="utf-8").read()) """) self.make_file("subdir/something", "hello") out = self.run_command("coverage run --source=. chdir.py") diff --git a/tests/test_report_common.py b/tests/test_report_common.py index 20c54e323..4583fe17d 100644 --- a/tests/test_report_common.py +++ b/tests/test_report_common.py @@ -265,7 +265,7 @@ def test_lcov(self) -> None: cov = coverage.Coverage() cov.load() cov.lcov_report() - with open("coverage.lcov") as lcov: + with open("coverage.lcov", encoding="utf-8") as lcov: actual = lcov.read() expected = textwrap.dedent("""\ SF:good.j2 diff --git a/tests/test_venv.py b/tests/test_venv.py index 70b2e4c4d..bd30b0e1b 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -62,7 +62,7 @@ def third(x): return 3 * x """) # Use plugin2.py as third.plugin - with open(os.path.join(os.path.dirname(__file__), "plugin2.py")) as f: + with open(os.path.join(os.path.dirname(__file__), "plugin2.py"), encoding="utf-8") as f: make_file("third_pkg/third/plugin.py", f.read()) # A render function for plugin2 to use for dynamic file names. make_file("third_pkg/third/render.py", """\ @@ -194,7 +194,7 @@ def in_venv_world_fixture(self, venv_world: Path) -> Iterator[None]: def get_trace_output(self) -> str: """Get the debug output of coverage.py""" - with open("debug_out.txt") as f: + with open("debug_out.txt", encoding="utf-8") as f: return f.read() @pytest.mark.parametrize('install_source_in_venv', [True, False]) diff --git a/tox.ini b/tox.ini index ef96551cc..dbc0f1f97 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,14 @@ setenv = pypy3{,9,10,11}: COVERAGE_TEST_CORES=pytrace # If we ever need a stronger way to suppress warnings: #PYTHONWARNINGS=ignore:removed in Python 3.14; use ast.Constant:DeprecationWarning + # We want to know about missing encoding arguments, but we need to silence + # some warnings that aren't ours. We can't silence them in 3.9 because + # EncodingWarning doesn't exist yet, and it's hard to suppress them in some + # environments and not others. So by default, don't warn, and will enable + # the warning in a handful of environments to catch the problems. + PYTHONWARNDEFAULTENCODING= + py3{10,11,12,13,14}: PYTHONWARNDEFAULTENCODING=1 + py3{10,11,12,13,14}: PYTHONWARNINGS=ignore::EncodingWarning:pip._internal.utils.subprocess # Disable CPython's color output PYTHON_COLORS=0