From 91db6835dda4f32bcde16f8a380464c8f582bf43 Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 11:59:02 +0900 Subject: [PATCH 01/11] ci: add CI on Windows using GitHub Action --- .github/workflows/windows-ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/windows-ci.yml diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml new file mode 100644 index 0000000..81febc0 --- /dev/null +++ b/.github/workflows/windows-ci.yml @@ -0,0 +1,14 @@ +name: Windows CI +on: [push, pull_request] + +jobs: + ci: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + - run: pip install tox + - name: Run tests + run: tox + env: + PYTEST_ADDOPTS: '-vv' From 9af0c85a64f5d87c402200ce5b5aa620ae5b20a6 Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 12:11:37 +0900 Subject: [PATCH 02/11] ci: set $TOXENV and take coverage after tests run successfully --- .github/workflows/windows-ci.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 81febc0..bcf92fd 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -3,12 +3,27 @@ on: [push, pull_request] jobs: ci: + name: Run tests + env: + TOXENV: py38-coverage runs-on: windows-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v1 - - run: pip install tox + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install tox - name: Run tests run: tox env: PYTEST_ADDOPTS: '-vv' + - name: Generate coverage report + run: | + .tox/$TOXENV/bin/coverage report -m + .tox/$TOXENV/bin/coverage xml + # TODO: Set secrets.CODECOV_TOKEN from repositroy settings and uncomment this + # - uses: codecov/codecov-action@v1 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + # yml: .codecov.yml From f93efc5581e5ba6113b331621953d02033b5bb6a Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 12:51:45 +0900 Subject: [PATCH 03/11] ci: also run Windows CI with Python 3.4, 3.5, 3.6, 3.7 --- .github/workflows/windows-ci.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index bcf92fd..e952b0a 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -4,12 +4,28 @@ on: [push, pull_request] jobs: ci: name: Run tests + strategy: + matrix: + py: [3.4, 3.5, 3.6, 3.7, 3.8] + include: + - py: 3.4 + tox: py34-coverage + - py: 3.5 + tox: py35-coverage + - py: 3.6 + tox: py36-coverage + - py: 3.7 + tox: py37-coverage + - py: 3.8 + tox: py38-coverage env: - TOXENV: py38-coverage + TOXENV: ${{ matrix.tox }} runs-on: windows-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.py }} - name: Install tox run: | python -m pip install --upgrade pip From cb9249a719a43a45a7f6f4d5ca2abdc423f7951d Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 12:57:28 +0900 Subject: [PATCH 04/11] ci: fix Python 3.4 is not supported on GitHub Action --- .github/workflows/windows-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index e952b0a..d50eff4 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -6,10 +6,8 @@ jobs: name: Run tests strategy: matrix: - py: [3.4, 3.5, 3.6, 3.7, 3.8] + py: [3.5, 3.6, 3.7, 3.8] include: - - py: 3.4 - tox: py34-coverage - py: 3.5 tox: py35-coverage - py: 3.6 From 36263b55a06ba6946e1714b5d3b1896bcb9b3eeb Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 12:58:25 +0900 Subject: [PATCH 05/11] ci: do not stop job when other job fails --- .github/workflows/windows-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index d50eff4..415daf0 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -5,6 +5,7 @@ jobs: ci: name: Run tests strategy: + fail-fast: false matrix: py: [3.5, 3.6, 3.7, 3.8] include: From 5655752df5253a5ac222374dc565531a7c0dbf48 Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 13:55:53 +0900 Subject: [PATCH 06/11] fix signal.SIGHUP is not available on Windows --- covimerage/cli.py | 10 ++++++---- tests/test_cli.py | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/covimerage/cli.py b/covimerage/cli.py index 1a16ade..7e8021e 100644 --- a/covimerage/cli.py +++ b/covimerage/cli.py @@ -149,10 +149,12 @@ def run(ctx, args, wrap_profile, profile_file, write_data, data_file, try: proc = subprocess.Popen(cmd) - def forward_signals(signalnum, stackframe): - """Forward SIGHUP to the subprocess.""" - proc.send_signal(signalnum) - signal.signal(signal.SIGHUP, forward_signals) + # signal.SIGHUP does not exist on Windows + if hasattr(signal, 'SIGHUP'): + def forward_signals(signalnum, stackframe): + """Forward SIGHUP to the subprocess.""" + proc.send_signal(signalnum) + signal.signal(signal.SIGHUP, forward_signals) try: exit_code = proc.wait() diff --git a/tests/test_cli.py b/tests/test_cli.py index 93de327..7215361 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -773,6 +773,7 @@ def test_run_report_without_data(tmpdir, runner, devnull): assert result.exit_code == 1 +@pytest.mark.skipif(sys.platform == 'win32', reason='SIGHUP is not available on Windows') def test_run_forwards_sighup(devnull): proc = subprocess.Popen([ sys.executable, '-m', 'covimerage', 'run', From c4aab3b451922a42bb6ed2b0f0b4485c116fdea0 Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 15:20:16 +0900 Subject: [PATCH 07/11] tests: use os.devnull instead of '/dev/null' for Windows CI --- tests/test_cli.py | 10 +++++----- tests/test_coveragepy.py | 3 ++- tests/test_main.py | 3 ++- tests/test_utils.py | 4 +++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 7215361..f4ccbbb 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -85,7 +85,7 @@ def test_cli_run_with_args_fd(capfd): def test_cli_run_subprocess_exception(runner, mocker): result = runner.invoke(cli.run, [os.devnull]) out = result.output.splitlines() - assert out[-1].startswith("Error: Failed to run ['/dev/null', '--cmd',") + assert out[-1].startswith("Error: Failed to run ['%s', '--cmd'," % os.devnull) assert '[Errno 13] Permission denied' in out[-1] assert result.exit_code == 1 @@ -136,7 +136,7 @@ def test_cli_run_args(runner, mocker, devnull, tmpdir): assert m.call_args[0] == (['printf', '--', '--headless'],) assert result.output.splitlines() == [ 'Running cmd: printf -- --headless (in %s)' % os.getcwd(), - 'Parsing profile file /dev/null.', + 'Parsing profile file %s.' % os.devnull, 'Not writing coverage file: no data to report!', 'Error: Command exited non-zero: 3.'] @@ -329,7 +329,7 @@ def test_cli_call_verbosity_fd(capfd): out, err = capfd.readouterr() assert out == '' assert err.splitlines() == [ - 'Parsing file: /dev/null', + 'Parsing file: %s' % os.devnull, 'source_files: []', 'Not writing coverage file: no data to report!', 'Error: No data to report.'] @@ -338,7 +338,7 @@ def test_cli_call_verbosity_fd(capfd): out, err = capfd.readouterr() assert out == '' assert err.splitlines() == [ - 'Parsing file: /dev/null', + 'Parsing file: %s' % os.devnull, 'source_files: []', 'Not writing coverage file: no data to report!', 'Error: No data to report.'] @@ -490,7 +490,7 @@ def test_report_profile_or_data_file(runner, tmpdir): 'report', '--data-file', os.devnull]) cov_exc = 'CoverageException: Doesn\'t seem to be a coverage.py data file' assert result.output.splitlines()[-1] == \ - 'Error: Coverage could not read data_file: /dev/null (%s)' % cov_exc + 'Error: Coverage could not read data_file: %s (%s)' % (os.devnull, cov_exc) assert result.exit_code == 1 with tmpdir.as_cwd(): diff --git a/tests/test_coveragepy.py b/tests/test_coveragepy.py index 9b04d56..4183648 100644 --- a/tests/test_coveragepy.py +++ b/tests/test_coveragepy.py @@ -1,3 +1,4 @@ +import os import sys import attr @@ -165,7 +166,7 @@ def test_coveragewrapper(coverage_fileobj, devnull): with pytest.raises(CoverageWrapperException) as excinfo: CoverageWrapper(data_file=devnull.name) assert excinfo.value.args == ( - 'Coverage could not read data_file: /dev/null',) + 'Coverage could not read data_file: %s' % os.devnull,) f = StringIO() with pytest.raises(CoverageWrapperException) as excinfo: diff --git a/tests/test_main.py b/tests/test_main.py index 62a2bc5..35da6a7 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,4 +1,5 @@ import logging +import os import textwrap import coverage @@ -39,7 +40,7 @@ def test_profile_fname_or_fobj(caplog, devnull): with caplog.at_level(logging.DEBUG, logger='covimerage'): Profile(devnull).parse() msgs = [(r.levelname, r.message) for r in caplog.records] - assert msgs == [('DEBUG', 'Parsing file: /dev/null')] + assert msgs == [('DEBUG', 'Parsing file: %s' % os.devnull)] fileobj = StringIO('') with caplog.at_level(logging.DEBUG, logger='covimerage'): diff --git a/tests/test_utils.py b/tests/test_utils.py index 117d567..f6194a9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,5 @@ +import os + from covimerage._compat import StringIO @@ -27,7 +29,7 @@ def test_get_fname_and_fobj_and_str(devnull): F = get_fname_and_fobj_and_str assert F('foo') == ('foo', None, 'foo') assert F(None) == (None, None, 'None') - assert F(devnull) == ('/dev/null', devnull, '/dev/null') + assert F(devnull) == (os.devnull, devnull, os.devnull) s = StringIO('') assert F(s) == (None, s, str(s)) From 8008a177a09eba5dc02291b922c58fdb81c234a7 Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 15:58:35 +0900 Subject: [PATCH 08/11] tests: normalize path separators in tests for Windows CI --- tests/test_cli.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index f4ccbbb..7709314 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,5 @@ import os +from os.path import normpath import signal import subprocess from subprocess import call @@ -221,7 +222,7 @@ def test_cli_run_can_skip_writing_data(with_append, runner, tmpdir): 'Parsing profile file %s.' % profile_file, 'Name Stmts Miss Cover', '----------------------------------------------------------------', - 'tests/test_plugin/conditional_function.vim 13 5 62%'] + normpath('tests/test_plugin/conditional_function.vim') + ' 13 5 62%'] assert not tmpdir.join(DEFAULT_COVERAGE_DATA_FILE).exists() @@ -267,7 +268,7 @@ def test_cli_run_report_fd(capfd, tmpdir, devnull): assert out.splitlines() == [ 'Name Stmts Miss Cover', '----------------------------------------------------------------', - 'tests/test_plugin/conditional_function.vim 13 5 62%'] + normpath('tests/test_plugin/conditional_function.vim') + ' 13 5 62%'] assert err.splitlines() == [ 'Running cmd: true (in %s)' % str(os.getcwd()), 'Parsing profile file %s.' % tmp_profile_fname, @@ -286,7 +287,7 @@ def test_cli_run_report_fd(capfd, tmpdir, devnull): assert open(ofname).read().splitlines() == [ 'Name Stmts Miss Cover', '----------------------------------------------------------------', - 'tests/test_plugin/conditional_function.vim 13 5 62%'] + normpath('tests/test_plugin/conditional_function.vim') + ' 13 5 62%'] def test_cli_call(capfd): @@ -511,7 +512,7 @@ def test_report_profile_or_data_file(runner, tmpdir): assert result.output.splitlines() == [ 'Name Stmts Miss Cover', '---------------------------------------------------------------', - 'tests/test_plugin/merged_conditionals.vim 19 12 37%'] + normpath('tests/test_plugin/merged_conditionals.vim') + ' 19 12 37%'] assert result.exit_code == 0 @@ -553,7 +554,7 @@ def test_report_source(runner, tmpdir, devnull): ) assert result.exit_code == 2 - fname = "foo/bar/test.vim" + fname = normpath("foo/bar/test.vim") tmpdir.join(fname).ensure().write("echom 1") tmpdir.join("foo/bar/test2.vim").ensure().write("echom 2") result = runner.invoke(cli.main, ["report", "--source", ".", devnull.name]) @@ -591,7 +592,7 @@ def test_report_source(runner, tmpdir, devnull): == [ "Name Stmts Miss Cover", "---------------------------------------------------------------", - "tests/test_plugin/merged_conditionals.vim 19 12 37%", + normpath("tests/test_plugin/merged_conditionals.vim") + " 19 12 37%", ] ) assert result.exit_code == 0 @@ -609,8 +610,8 @@ def test_cli_xml(runner, tmpdir): with open('coverage.xml') as f: xml = f.read() - assert 'filename="%s/tests/test_plugin/merged_conditionals.vim' % ( - old_cwd) in xml + assert 'filename="%s' % ( + old_cwd.join('/tests/test_plugin/merged_conditionals.vim')) in xml # --rcfile is used. coveragerc = 'customrc' @@ -624,8 +625,8 @@ def test_cli_xml(runner, tmpdir): assert result.exit_code == 0 with open('custom.xml') as f: xml = f.read() - assert 'filename="%s/tests/test_plugin/merged_conditionals.vim' % ( - old_cwd) in xml + assert 'filename="%s' % ( + old_cwd.join('/tests/test_plugin/merged_conditionals.vim')) in xml # --rcfile is used. coveragerc = 'customrc' @@ -722,7 +723,7 @@ def run_args(profile_file): 'Writing coverage file .coverage_covimerage.', 'Name Stmts Miss Cover', '----------------------------------------------------------------', - 'tests/test_plugin/conditional_function.vim 13 5 62%'] + normpath('tests/test_plugin/conditional_function.vim') + ' 13 5 62%'] assert result.exit_code == 0 assert open('.coverage_covimerage').read().startswith(covdata_header) @@ -735,7 +736,7 @@ def run_args(profile_file): 'Writing coverage file .coverage_covimerage.', 'Name Stmts Miss Cover', '----------------------------------------------------------------', - 'tests/test_plugin/conditional_function.vim 13 5 62%'] + normpath('tests/test_plugin/conditional_function.vim') + ' 13 5 62%'] assert result.exit_code == 0 # Append another profile. @@ -753,8 +754,8 @@ def run_args(profile_file): 'Writing coverage file .coverage_covimerage.', 'Name Stmts Miss Cover', '----------------------------------------------------------------', - 'tests/test_plugin/conditional_function.vim 13 5 62%', - 'tests/test_plugin/merged_conditionals.vim 19 12 37%', + normpath('tests/test_plugin/conditional_function.vim') + ' 13 5 62%', + normpath('tests/test_plugin/merged_conditionals.vim') + ' 19 12 37%', '----------------------------------------------------------------', 'TOTAL 32 17 47%'] assert result.exit_code == 0 From 78cfb0c0cadf9c4045ddb70eb408e22dbcfd1030 Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 16:17:32 +0900 Subject: [PATCH 09/11] tests: fix command outputs includes \r\n on Windows --- tests/test_cli.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 7709314..5bee96f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -13,6 +13,8 @@ from covimerage import DEFAULT_COVERAGE_DATA_FILE, cli, get_version from covimerage.cli import get_version_message +NEWLINE = '\n' if sys.platform != 'win32' else '\r\n' + def test_dunder_main_run(capfd): assert call([sys.executable, '-m', 'covimerage']) == 0 @@ -293,7 +295,7 @@ def test_cli_run_report_fd(capfd, tmpdir, devnull): def test_cli_call(capfd): assert call(['covimerage', '--version']) == 0 out, err = capfd.readouterr() - assert out == get_version_message() + '\n' + assert out == get_version_message() + NEWLINE assert call(['covimerage', '--help']) == 0 out, err = capfd.readouterr() @@ -354,7 +356,7 @@ def test_cli_call_verbosity_fd(capfd): assert call(['covimerage', '-qq', 'write_coverage', os.devnull]) == 1 out, err = capfd.readouterr() assert out == '' - assert err == 'Error: No data to report.\n' + assert err == 'Error: No data to report.' + NEWLINE def test_cli_writecoverage_without_data(runner): @@ -662,7 +664,7 @@ def test_run_handles_exit_code_from_python_fd(capfd): 'python', '-c', 'print("output"); import sys; sys.exit(42)']) out, err = capfd.readouterr() assert 'Error: Command exited non-zero: 42.' in err.splitlines() - assert out == 'output\n' + assert out == 'output' + NEWLINE assert ret == 42 From 9911b01730241792084982151f360ebf2718925a Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 16:25:55 +0900 Subject: [PATCH 10/11] tests: consider WinError in tests for subprocess failure --- tests/test_cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 5bee96f..3d532db 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -89,7 +89,10 @@ def test_cli_run_subprocess_exception(runner, mocker): result = runner.invoke(cli.run, [os.devnull]) out = result.output.splitlines() assert out[-1].startswith("Error: Failed to run ['%s', '--cmd'," % os.devnull) - assert '[Errno 13] Permission denied' in out[-1] + expected_message = '[Errno 13] Permission denied' + if sys.platform == 'win32': + expected_message = '[WinError 2] The system cannot find the file specified' + assert expected_message in out[-1] assert result.exit_code == 1 From d3d8400b3f3a01f3fffbfba688a271550133e4bb Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 30 Jan 2020 16:31:41 +0900 Subject: [PATCH 11/11] tests: fix test failures due to file paths on Windows CI --- tests/test_cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 3d532db..0a37326 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -522,7 +522,7 @@ def test_report_profile_or_data_file(runner, tmpdir): def test_report_rcfile_and_include(tmpdir, runner): - profiled_file = 'tests/test_plugin/conditional_function.vim' + profiled_file = normpath('tests/test_plugin/conditional_function.vim') profiled_file_content = open(profiled_file, 'r').read() # Works without rcfile. @@ -616,7 +616,7 @@ def test_cli_xml(runner, tmpdir): with open('coverage.xml') as f: xml = f.read() assert 'filename="%s' % ( - old_cwd.join('/tests/test_plugin/merged_conditionals.vim')) in xml + old_cwd.join('tests/test_plugin/merged_conditionals.vim')) in xml # --rcfile is used. coveragerc = 'customrc' @@ -631,7 +631,7 @@ def test_cli_xml(runner, tmpdir): with open('custom.xml') as f: xml = f.read() assert 'filename="%s' % ( - old_cwd.join('/tests/test_plugin/merged_conditionals.vim')) in xml + old_cwd.join('tests/test_plugin/merged_conditionals.vim')) in xml # --rcfile is used. coveragerc = 'customrc'