Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add windows CI #1023

Merged
merged 24 commits into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,72 @@ jobs:
name: codecov-umbrella
fail_ci_if_error: false

build_windows_without_ops:
runs-on: windows-latest
env:
MMCV_WITH_OPS: 0
strategy:
matrix:
torch: [1.7.1, 1.8.0, 1.9.0]
include:
- torch: 1.7.1
torchvision: 0.8.2
- torch: 1.8.0
torchvision: 0.9.0
- torch: 1.9.0
torchvision: 0.10.0
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install Pillow
run: pip install Pillow==6.2.2
if: ${{matrix.torchvision == '0.4.2'}}
- name: Install PyTorch
run: pip install torch==${{matrix.torch}}+cpu torchvision==${{matrix.torchvision}}+cpu --no-cache-dir -f https://download.pytorch.org/whl/torch_stable.html
- name: Build and install
run: pip install -e .
- name: Validate the installation
run: python -c "import mmcv"
- name: Run unittests
run: |
pip install -r requirements/test.txt
pytest tests/ --ignore=tests/test_ops --ignore tests/test_utils/test_progressbar.py --ignore tests/test_utils/test_timer.py --ignore tests/test_image/test_io.py

build_windows:
runs-on: windows-latest
strategy:
matrix:
torch: [1.7.1, 1.8.0, 1.9.0]
include:
- torch: 1.7.1
torchvision: 0.8.2
zhouzaida marked this conversation as resolved.
Show resolved Hide resolved
- torch: 1.8.0
torchvision: 0.9.0
- torch: 1.9.0
torchvision: 0.10.0
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install Pillow
run: pip install Pillow==6.2.2
if: ${{matrix.torchvision == '0.4.2'}}
- name: Install PyTorch
run: pip install torch==${{matrix.torch}}+cpu torchvision==${{matrix.torchvision}}+cpu --no-cache-dir -f https://download.pytorch.org/whl/torch_stable.html
- name: Build and install
run: pip install -e .
- name: Validate the installation
run: python -c "import mmcv"
- name: Run unittests
run: |
pip install -r requirements/test.txt
pytest tests/ --ignore tests/test_utils/test_progressbar.py --ignore tests/test_utils/test_timer.py --ignore tests/test_image/test_io.py

build_macos:
runs-on: macos-latest
strategy:
Expand Down
4 changes: 3 additions & 1 deletion mmcv/runner/hooks/logger/pavi.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ def log(self, runner):
def after_run(self, runner):
if self.add_last_ckpt:
ckpt_path = osp.join(runner.work_dir, 'latest.pth')
if osp.isfile(ckpt_path):
if osp.islink(ckpt_path):
ckpt_path = osp.join(runner.work_dir, os.readlink(ckpt_path))
ZwwWayne marked this conversation as resolved.
Show resolved Hide resolved

if osp.isfile(ckpt_path):
# runner.epoch += 1 has been done before `after_run`.
iteration = runner.epoch if self.by_epoch else runner.iter
return self.writer.add_snapshot_file(
Expand Down
3 changes: 2 additions & 1 deletion mmcv/video/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def concat_video(video_list,
log_level (str): Logging level of ffmpeg.
print_cmd (bool): Whether to print the final ffmpeg command.
"""
_, tmp_filename = tempfile.mkstemp(suffix='.txt', text=True)
tmp_filehandler, tmp_filename = tempfile.mkstemp(suffix='.txt', text=True)
with open(tmp_filename, 'w') as f:
for filename in video_list:
f.write(f'file {osp.abspath(filename)}\n')
Expand All @@ -156,4 +156,5 @@ def concat_video(video_list,
print_cmd,
pre_options='-f concat -safe 0',
**options)
os.close(tmp_filehandler)
os.remove(tmp_filename)
8 changes: 7 additions & 1 deletion tests/test_runner/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""
import logging
import os.path as osp
import platform
import random
import re
import shutil
Expand Down Expand Up @@ -207,9 +208,14 @@ def test_pavi_hook():
'learning_rate': 0.02,
'momentum': 0.95
}, 1)
# in windows environment, the latest checkpoint is copied from epoch_1.pth
if platform.system() == 'Windows':
snapshot_file_path = osp.join(runner.work_dir, 'latest.pth')
else:
snapshot_file_path = osp.join(runner.work_dir, 'epoch_1.pth')
hook.writer.add_snapshot_file.assert_called_with(
tag=runner.work_dir.split('/')[-1],
snapshot_file_path=osp.join(runner.work_dir, 'epoch_1.pth'),
snapshot_file_path=snapshot_file_path,
iteration=1)


Expand Down
8 changes: 7 additions & 1 deletion tests/test_runner/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import os
import os.path as osp
import platform
import random
import string
import tempfile
Expand Down Expand Up @@ -169,7 +170,12 @@ def test_save_checkpoint(runner_class):
first_ckp_path = osp.join(root, 'iter_1.pth')

assert osp.exists(first_ckp_path)
assert osp.realpath(latest_path) == osp.realpath(first_ckp_path)

if platform.system() != 'Windows':
assert osp.realpath(latest_path) == osp.realpath(first_ckp_path)
else:
# use copy instead of symlink on windows
pass

torch.load(latest_path)

Expand Down
27 changes: 17 additions & 10 deletions tests/test_utils/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os.path as osp
import shutil
import tempfile
from pathlib import Path

import pytest
import yaml
Expand Down Expand Up @@ -66,10 +67,14 @@ def test_construct():

# test h.py
cfg_file = osp.join(data_path, 'config/h.py')
cfg_dict = dict(
item1='h.py',
item2=f'{osp.dirname(__file__)}/data/config',
item3='abc_h')
path = osp.join(osp.dirname(__file__), 'data', 'config')
# the value of osp.dirname(__file__) may be `D:\a\xxx` in windows
# environment. When dumping the cfg_dict to file, `D:\a\xxx` will be
# converted to `D:\x07\xxx` and it will cause unexpected result when
# checking whether `D:\a\xxx` equals to `D:\x07\xxx`. Therefore, we forcely
# convert a string representation of the path with forward slashes (/)
path = Path(path).as_posix()
cfg_dict = dict(item1='h.py', item2=path, item3='abc_h')
cfg = Config(cfg_dict, filename=cfg_file)
assert isinstance(cfg, Config)
assert cfg.filename == cfg_file
Expand All @@ -96,7 +101,7 @@ def test_construct():

# test p.yaml
cfg_file = osp.join(data_path, 'config/p.yaml')
cfg_dict = dict(item1=f'{osp.dirname(__file__)}/data/config')
cfg_dict = dict(item1=osp.join(osp.dirname(__file__), 'data', 'config'))
cfg = Config(cfg_dict, filename=cfg_file)
assert isinstance(cfg, Config)
assert cfg.filename == cfg_file
Expand All @@ -115,7 +120,7 @@ def test_construct():

# test o.json
cfg_file = osp.join(data_path, 'config/o.json')
cfg_dict = dict(item1=f'{osp.dirname(__file__)}/data/config')
cfg_dict = dict(item1=osp.join(osp.dirname(__file__), 'data', 'config'))
cfg = Config(cfg_dict, filename=cfg_file)
assert isinstance(cfg, Config)
assert cfg.filename == cfg_file
Expand Down Expand Up @@ -490,17 +495,19 @@ def test_reserved_key():


def test_syntax_error():
temp_cfg_file = tempfile.NamedTemporaryFile(suffix='.py')
# the name can not be used to open the file a second time in windows,
# so `delete` should be set as `False` and we need to manually remove it
# more details can be found at https://github.com/open-mmlab/mmcv/pull/1077
temp_cfg_file = tempfile.NamedTemporaryFile(suffix='.py', delete=False)
temp_cfg_path = temp_cfg_file.name
# write a file with syntax error
with open(temp_cfg_path, 'w') as f:
f.write('a=0b=dict(c=1)')
with pytest.raises(
SyntaxError,
match='There are syntax errors in config '
f'file {temp_cfg_path}'):
SyntaxError, match='There are syntax errors in config file'):
Config.fromfile(temp_cfg_path)
temp_cfg_file.close()
os.remove(temp_cfg_path)


def test_pickle_support():
Expand Down
45 changes: 33 additions & 12 deletions tests/test_utils/test_logging.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os
import platform
import tempfile
from unittest.mock import patch
Expand Down Expand Up @@ -28,15 +29,21 @@ def test_get_logger_rank0():
assert len(logger.handlers) == 1
assert logger.handlers[0].level == logging.DEBUG

with tempfile.NamedTemporaryFile() as f:
# the name can not be used to open the file a second time in windows,
# so `delete` should be set as `False` and we need to manually remove it
# more details can be found at https://github.com/open-mmlab/mmcv/pull/1077
with tempfile.NamedTemporaryFile(delete=False) as f:
logger = get_logger('rank0.pkg3', log_file=f.name)
assert isinstance(logger, logging.Logger)
assert len(logger.handlers) == 2
assert isinstance(logger.handlers[0], logging.StreamHandler)
assert isinstance(logger.handlers[1], logging.FileHandler)
assert isinstance(logger, logging.Logger)
assert len(logger.handlers) == 2
assert isinstance(logger.handlers[0], logging.StreamHandler)
assert isinstance(logger.handlers[1], logging.FileHandler)
logger_pkg3 = get_logger('rank0.pkg3')
assert id(logger_pkg3) == id(logger)
# flushing and closing all handlers in order to remove `f.name`
logging.shutdown()

logger_pkg3 = get_logger('rank0.pkg3')
assert id(logger_pkg3) == id(logger)
os.remove(f.name)

logger_pkg3 = get_logger('rank0.pkg3.subpkg')
assert logger_pkg3.handlers == logger_pkg3.handlers
Expand All @@ -52,11 +59,18 @@ def test_get_logger_rank1():
assert isinstance(logger.handlers[0], logging.StreamHandler)
assert logger.handlers[0].level == logging.INFO

with tempfile.NamedTemporaryFile() as f:
# the name can not be used to open the file a second time in windows,
# so `delete` should be set as `False` and we need to manually remove it
# more details can be found at https://github.com/open-mmlab/mmcv/pull/1077
with tempfile.NamedTemporaryFile(delete=False) as f:
logger = get_logger('rank1.pkg2', log_file=f.name)
assert isinstance(logger, logging.Logger)
assert len(logger.handlers) == 1
assert logger.handlers[0].level == logging.INFO
assert isinstance(logger, logging.Logger)
assert len(logger.handlers) == 1
assert logger.handlers[0].level == logging.INFO
# flushing and closing all handlers in order to remove `f.name`
logging.shutdown()

os.remove(f.name)


def test_print_log_print(capsys):
Expand All @@ -79,7 +93,10 @@ def test_print_log_logger(caplog):
print_log('welcome', logger='mmcv', level=logging.ERROR)
assert caplog.record_tuples[-1] == ('mmcv', logging.ERROR, 'welcome')

with tempfile.NamedTemporaryFile() as f:
# the name can not be used to open the file a second time in windows,
# so `delete` should be set as `False` and we need to manually remove it
# more details can be found at https://github.com/open-mmlab/mmcv/pull/1077
with tempfile.NamedTemporaryFile(delete=False) as f:
logger = get_logger('abc', log_file=f.name)
print_log('welcome', logger=logger)
assert caplog.record_tuples[-1] == ('abc', logging.INFO, 'welcome')
Expand All @@ -89,6 +106,10 @@ def test_print_log_logger(caplog):
match = re.fullmatch(regex_time + r' - abc - INFO - welcome\n',
log_text)
assert match is not None
# flushing and closing all handlers in order to remove `f.name`
logging.shutdown()

os.remove(f.name)


def test_print_log_exception():
Expand Down
7 changes: 5 additions & 2 deletions tests/test_utils/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ def test_scandir():
])
assert set(mmcv.scandir(folder, '.png')) == set()

# path of sep is `\\` in windows but `/` in linux, so osp.join should be
# used to join string for compatibility
filenames_recursive = [
'a.bin', '1.txt', '2.txt', '1.json', '2.json', 'sub/1.json',
'sub/1.txt', '.file'
'a.bin', '1.txt', '2.txt', '1.json', '2.json',
osp.join('sub', '1.json'),
osp.join('sub', '1.txt'), '.file'
]
# .file starts with '.' and is a file so it will not be scanned
assert set(mmcv.scandir(folder, recursive=True)) == set(
Expand Down
3 changes: 2 additions & 1 deletion tests/test_video/test_optflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ def test_flowwrite():
flow = np.random.rand(100, 100, 2).astype(np.float32)

# write to a .flo file
_, filename = tempfile.mkstemp()
tmp_filehandler, filename = tempfile.mkstemp()
mmcv.flowwrite(flow, filename)
flow_from_file = mmcv.flowread(filename)
assert_array_equal(flow, flow_from_file)
os.close(tmp_filehandler)
os.remove(filename)

# write to two .jpg files
Expand Down
7 changes: 6 additions & 1 deletion tests/test_video/test_processing.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Copyright (c) Open-MMLab. All rights reserved.
# Copyright (c) OpenMMLab. All rights reserved.
import os
import os.path as osp
import platform
import tempfile

import pytest

import mmcv


Expand All @@ -13,6 +16,7 @@ def setup_class(cls):
cls.video_path = osp.join(osp.dirname(__file__), '../data/test.mp4')
cls.num_frames = 168

@pytest.mark.skipif(platform.system() == 'Windows', reason='skip windows')
def test_cut_concat_video(self):
part1_file = osp.join(tempfile.gettempdir(), '.mmcv_test1.mp4')
part2_file = osp.join(tempfile.gettempdir(), '.mmcv_test2.mp4')
Expand All @@ -31,6 +35,7 @@ def test_cut_concat_video(self):
os.remove(part2_file)
os.remove(out_file)

@pytest.mark.skipif(platform.system() == 'Windows', reason='skip windows')
def test_resize_video(self):
out_file = osp.join(tempfile.gettempdir(), '.mmcv_test.mp4')
mmcv.resize_video(
Expand Down
16 changes: 8 additions & 8 deletions tests/test_video/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,12 @@ def test_frames2video(self):
start=10,
end=50,
show_progress=False)
v = mmcv.VideoReader(out_filename)
assert v.fps == 25
assert len(v) == 40

for i in range(self.num_frames):
filename = f'{frame_dir}/{i:06d}.jpg'
os.remove(filename)
shutil.rmtree(frame_dir)
os.remove(out_filename)
with mmcv.VideoReader(out_filename) as v:
assert v.fps == 25
assert len(v) == 40

for i in range(self.num_frames):
filename = f'{frame_dir}/{i:06d}.jpg'
os.remove(filename)
shutil.rmtree(frame_dir)