diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 28e04ad8d7..6e46def0fc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,7 +58,7 @@ jobs: matrix: os: - 'ubuntu-18.04' -# - 'windows-2019' + - 'windows-2019' - 'macos-10.15' python: - '3.6' diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 0fcbe57e25..61d714fa8c 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -49,7 +49,7 @@ jobs: matrix: os: - 'ubuntu-18.04' -# - 'windows-2019' + - 'windows-2019' - 'macos-10.15' python: - '3.6' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 309b23aa22..013659ea42 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: matrix: os: - 'ubuntu-18.04' - # - 'windows-2019' # need to be fixed, see: https://github.com/opendilab/treevalue/issues/41 + - 'windows-2019' # need to be fixed, see: https://github.com/opendilab/treevalue/issues/41 - 'macos-10.15' python-version: - '3.6' @@ -29,21 +29,21 @@ jobs: steps: - name: Get system version for Linux - if: ${{ contains(matrix.os, 'ubuntu') }} + if: ${{ runner.os == 'Linux' }} shell: bash run: | echo "OS_NAME=Linux" >> $GITHUB_ENV echo "IS_WIN=" >> $GITHUB_ENV echo "IS_MAC=" >> $GITHUB_ENV - name: Get system version for Windows - if: ${{ contains(matrix.os, 'windows') }} + if: ${{ runner.os == 'Windows' }} shell: bash run: | echo "OS_NAME=Windows" >> $GITHUB_ENV echo "IS_WIN=1" >> $GITHUB_ENV echo "IS_MAC=" >> $GITHUB_ENV - name: Get system version for MacOS - if: ${{ contains(matrix.os, 'macos') }} + if: ${{ runner.os == 'macOS' }} shell: bash run: | echo "OS_NAME=MacOS" >> $GITHUB_ENV diff --git a/pyproject.toml b/pyproject.toml index f0742e0f17..1cb4bff966 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,11 +8,23 @@ requires = [ [tool.cibuildwheel] skip = ["pp*"] # Do not build for PyPy +## Windows build configuration +[tool.cibuildwheel.windows] +archs = ["x86", 'AMD64'] +before-test = [# Unittest for windows + "pip install -r \"{project}\\requirements-test-win.txt\"", +] +test-command = [ + "xcopy /e /i \"{project}\\test\" test", + "copy \"{project}\\pytest.ini\" pytest.ini", + "pytest test -sv -m unittest --log-level=DEBUG", + "rmdir /s /q test", +] ## macOS build configuration [tool.cibuildwheel.macos] archs = ["x86_64", "arm64"] # Build for x86_64 and arm64 -before-test = [# Unittest for linux +before-test = [# Unittest for macos "pip install -r {project}/requirements-test.txt", ] test-command = [ diff --git a/requirements-test-win.txt b/requirements-test-win.txt new file mode 100644 index 0000000000..eab8a2433b --- /dev/null +++ b/requirements-test-win.txt @@ -0,0 +1,15 @@ +coverage>=5 +mock>=4.0.3 +flake8~=3.5 +pytest~=6.2.5 +pytest-cov~=3.0.0 +pytest-mock~=3.6.1 +pytest-xdist>=1.34.0 +pytest-rerunfailures~=10.2 +pytest-timeout~=2.0.2 +pytest-benchmark~=3.4.0 +testtools>=2 +hbutils>=0.6.13 +setuptools<=59.5.0 +numpy>=1.10 +easydict>=1.7,<2 \ No newline at end of file diff --git a/test/entry/cli/test_export.py b/test/entry/cli/test_export.py index 907c79d240..2eef7f2927 100644 --- a/test/entry/cli/test_export.py +++ b/test/entry/cli/test_export.py @@ -30,7 +30,8 @@ def test_simple_export(self): with runner.isolated_filesystem(): result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.t1']) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('t1.btv') assert os.path.getsize('t1.btv') < 240 @@ -42,7 +43,8 @@ def test_multiple_export(self): with runner.isolated_filesystem(): result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*']) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('t1.btv') assert os.path.getsize('t1.btv') < 240 assert os.path.exists('t2.btv') @@ -60,7 +62,8 @@ def test_multiple_export(self): with runner.isolated_filesystem(): result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.t[23]']) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert not os.path.exists('t1.btv') assert os.path.exists('t2.btv') assert os.path.getsize('t2.btv') < 290 @@ -78,7 +81,8 @@ def test_multiple_export_without_compression(self): with pytest.warns(None): result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*', '-r']) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('t1.btv') assert os.path.getsize('t1.btv') < 2170 assert os.path.exists('t2.btv') @@ -98,7 +102,8 @@ def test_multiple_export_without_compression(self): result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*', '-r', '-c', 'zlib']) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('t1.btv') assert os.path.getsize('t1.btv') < 2170 assert os.path.exists('t2.btv') @@ -119,7 +124,8 @@ def test_multiple_export_with_compress_define(self): result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*', '-c', 'gzip']) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('t1.btv') assert os.path.getsize('t1.btv') < 220 assert os.path.exists('t2.btv') @@ -140,7 +146,8 @@ def test_multiple_export_with_compress_define(self): '-c', 'test.entry.cli.test_export._my_compress:test.entry.cli.test_export._my_decompress' ]) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('t1.btv') assert os.path.getsize('t1.btv') < 330 assert os.path.exists('t2.btv') @@ -167,7 +174,8 @@ def test_with_od(self): '-o', 'subpath/t3.btv', ]) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('subpath/t1.btv') assert os.path.getsize('subpath/t1.btv') < 290 assert os.path.exists('subpath/t2.btv') @@ -190,7 +198,8 @@ def test_with_od(self): '-d', 'subpath', ]) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('subpath/t1.btv') assert os.path.getsize('subpath/t1.btv') < 290 assert os.path.exists('subpath/t2.btv') @@ -210,7 +219,8 @@ def test_with_od(self): '-d', 'subpath', ]) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert not os.path.exists('subpath/t1.btv') assert os.path.exists('subpath/t2.btv') assert os.path.getsize('subpath/t2.btv') < 290 @@ -229,7 +239,8 @@ def test_with_od(self): '-o', 'ppp.btv' ]) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert not os.path.exists('t1.btv') assert not os.path.exists('t2.btv') assert os.path.exists('ppp.btv') diff --git a/test/entry/cli/test_graph.py b/test/entry/cli/test_graph.py index 9fe6bdfec5..a88b2a636c 100644 --- a/test/entry/cli/test_graph.py +++ b/test/entry/cli/test_graph.py @@ -7,7 +7,7 @@ import pytest from click.testing import CliRunner -from hbutils.testing import cmdv +from hbutils.testing import cmdv, OS from treevalue import FastTreeValue, dump, graphics from treevalue.entry.cli import treevalue_cli @@ -36,7 +36,8 @@ def test_simple_code_graph(self): '-o', 'test_graph.svg', '-o', 'test_graph.gv'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 7000 assert os.path.exists('test_graph.gv') @@ -52,7 +53,8 @@ def test_simple_code_graph_to_stdout(self): '-o', 'test_graph.svg', '-o', 'test_graph.gv', '-O'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert not os.path.exists('test_graph.svg') assert not os.path.exists('test_graph.gv') assert len(result.output) <= 2500 @@ -66,7 +68,8 @@ def test_simple_code_multiple_graph(self): args=['graph', '-t', 'test.entry.cli.test_graph.t[12]', '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 13000 @@ -76,7 +79,8 @@ def test_simple_code_multiple_graph(self): args=['graph', '-t', 'test.entry.cli.test_graph.*', '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 17500 @@ -92,7 +96,8 @@ def test_simple_binary_graph(self): args=['graph', '-t', 'g1.bg', '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 6500 @@ -109,7 +114,8 @@ def test_simple_binary_graph(self): args=['graph', '-t', '*.bg', '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 17500 @@ -122,7 +128,8 @@ def test_simple_binary_graph(self): args=['graph', '-t', 'test.entry.cli.test_graph.t1', '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 13000 @@ -135,7 +142,8 @@ def test_simple_binary_graph(self): args=['graph', '-t', 'test.entry.cli.test_graph.t1', '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 6500 @@ -149,7 +157,8 @@ def test_duplicates(self): '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 12000 @@ -162,7 +171,8 @@ def test_duplicates(self): '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') import shutil shutil.copy('test_graph.svg', os.path.join(_p, 'test_graph.svg')) @@ -179,7 +189,8 @@ def test_graph(self): '-t', 'first title', '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') assert os.path.getsize('test_graph.svg') <= 16500 @@ -196,7 +207,8 @@ def test_cfg(self): '-c', 'bgcolor=#ffffff00', '-O'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert len(result.output) <= 6000 assert '#ffffff00' in result.output @@ -207,7 +219,9 @@ def test_cfg(self): '-c', 'bgcolor#ffffff00', '-O'], ) - assert result.exit_code != 0 + assert result.exit_code != 0, f'The running expected to raise RuntimeError ' \ + f'but not actually (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert "Configuration should be KEY=VALUE, but 'bgcolor#ffffff00' found." in result.output @unittest.skipUnless(cmdv('dot'), 'Dot installed only') @@ -227,6 +241,10 @@ def test_file_with_invalid_permission(self): args=['graph', '-t', 'g1.bg', '-o', 'test_graph.svg'], ) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert os.path.exists('test_graph.svg') - assert os.path.getsize('test_graph.svg') <= 1000 + if OS.windows: + assert os.path.getsize('test_graph.svg') <= 2000 + else: + assert os.path.getsize('test_graph.svg') <= 1000 diff --git a/test/entry/cli/test_version.py b/test/entry/cli/test_version.py index 8a5b3771b3..689be95f4f 100644 --- a/test/entry/cli/test_version.py +++ b/test/entry/cli/test_version.py @@ -11,6 +11,7 @@ def test_version(self): runner = CliRunner() result = runner.invoke(treevalue_cli, args=['-v']) - assert result.exit_code == 0 + assert result.exit_code == 0, f'Runtime Error (exitcode {result.exit_code}), ' \ + f'The output is:\n{result.output}' assert __TITLE__.lower() in result.stdout.lower() assert __VERSION__.lower() in result.stdout.lower() diff --git a/test/tree/general/test_general_benchmark.py b/test/tree/general/test_general_benchmark.py index 66ab359b85..88bbb64d91 100644 --- a/test/tree/general/test_general_benchmark.py +++ b/test/tree/general/test_general_benchmark.py @@ -1,19 +1,36 @@ +import unittest +from functools import lru_cache +from typing import Optional + +from hbutils.testing import vpip + +try: + import torch +except ImportError: + torch = None + import pytest -import torch from treevalue import TreeValue, func_treelize, FastTreeValue -_TREE_DATA_1 = {'a': torch.randn(2, 3), 'x': {'c': torch.randn(3, 4)}} -_TREE_1 = FastTreeValue(_TREE_DATA_1) + +@lru_cache() +def _get_tree() -> Optional[FastTreeValue]: + if torch is not None: + _TREE_DATA_1 = {'a': torch.randn(2, 3), 'x': {'c': torch.randn(3, 4)}} + return FastTreeValue(_TREE_DATA_1) + else: + return None @pytest.mark.benchmark(group='treevalue_dynamic') +@unittest.skipUnless(vpip('torch') >= '1.1.0', 'Torch>=1.1.0 only') class TestTreeGeneralBenchmark: def test_dynamic_execute(self, benchmark): def sin(t): return t.sin() - return benchmark(sin, _TREE_1) + return benchmark(sin, _get_tree()) def test_static_execute(self, benchmark): sinf = func_treelize(return_type=TreeValue)(torch.sin) @@ -21,4 +38,4 @@ def test_static_execute(self, benchmark): def sin(t): return sinf(t) - return benchmark(sin, _TREE_1) + return benchmark(sin, _get_tree()) diff --git a/test/tree/general/test_tensor.py b/test/tree/general/test_tensor.py index 26a462d5b4..2574ab322d 100644 --- a/test/tree/general/test_tensor.py +++ b/test/tree/general/test_tensor.py @@ -1,10 +1,18 @@ +import unittest + import pytest -import torch +from hbutils.testing import vpip + +try: + import torch +except ImportError: + torch = None from treevalue.tree import func_treelize, FastTreeValue @pytest.mark.unittest +@unittest.skipUnless(vpip('torch') >= '1.1.0', 'Torch>=1.1.0 only') def test_for_torch_support(): sin = func_treelize()(torch.sin) cos = func_treelize()(torch.cos) # the same sin function diff --git a/test/utils/test_formattree.py b/test/utils/test_formattree.py index 89e1fa3c0a..f970ac1dc5 100644 --- a/test/utils/test_formattree.py +++ b/test/utils/test_formattree.py @@ -19,6 +19,8 @@ # And in this library 'treevalue', Apache Licence Version 2.0 is used as well. import doctest +import io +import os from operator import itemgetter from textwrap import dedent @@ -31,6 +33,11 @@ ) +def _process_linesep(s: str) -> str: + with io.StringIO(s) as sf: + return os.linesep.join([line.rstrip('\r\n') for line in sf]) + + @pytest.mark.unittest class TestFormatTree(TestCase): @@ -40,9 +47,9 @@ def format_tree(self, tree, encoding='utf8'): def test_single_node_tree(self): tree = ('foo', []) output = self.format_tree(tree) - self.assertEqual(dedent(u'''\ + self.assertEqual(_process_linesep(dedent(u'''\ foo - '''), output) + ''')), _process_linesep(output)) def test_single_level_tree(self): tree = ( @@ -53,12 +60,12 @@ def test_single_level_tree(self): ], ) output = self.format_tree(tree) - self.assertEqual(dedent(u'''\ + self.assertEqual(_process_linesep(dedent(u'''\ foo ├── bar ├── baz └── qux - '''), output) + ''')), _process_linesep(output)) def test_single_level_tree_with_ascii(self): tree = ( @@ -69,12 +76,12 @@ def test_single_level_tree_with_ascii(self): ], ) output = self.format_tree(tree, encoding='ascii') - self.assertEqual(dedent(u'''\ + self.assertEqual(_process_linesep(dedent(u'''\ foo +-- bar +-- baz `-- qux - '''), output) + ''')), _process_linesep(output)) def test_multi_level_tree(self): tree = ( @@ -88,14 +95,14 @@ def test_multi_level_tree(self): ], ) output = self.format_tree(tree) - self.assertEqual(dedent(u'''\ + self.assertEqual(_process_linesep(dedent(u'''\ foo ├── bar │ ├── a │ └── b ├── baz └── qux - '''), output) + ''')), _process_linesep(output)) def test_multi_level_on_last_node_tree(self): tree = ( @@ -109,14 +116,14 @@ def test_multi_level_on_last_node_tree(self): ], ) output = self.format_tree(tree) - self.assertEqual(dedent(u'''\ + self.assertEqual(_process_linesep(dedent(u'''\ foo ├── bar ├── baz └── qux ├── a └── b - '''), output) + ''')), _process_linesep(output)) def test_acceptance(self): output = self.format_tree(ACCEPTANCE_INPUT) @@ -140,7 +147,7 @@ def test_newlines(self): ], ) output = self.format_tree(tree) - self.assertEqual(dedent(u'''\ + self.assertEqual(_process_linesep(dedent(u'''\ foo ├── bar │ frob @@ -151,7 +158,7 @@ def test_newlines(self): ├── baz └── qux frab - '''), output) + ''')), _process_linesep(output)) def d(name, files): diff --git a/treevalue/entry/cli/graph.py b/treevalue/entry/cli/graph.py index 0c43e3d8c2..db45bc540f 100644 --- a/treevalue/entry/cli/graph.py +++ b/treevalue/entry/cli/graph.py @@ -1,7 +1,5 @@ import codecs import os -import shutil -import tempfile import warnings from collections import OrderedDict from functools import partial @@ -61,10 +59,7 @@ def _save_source_code(g: Digraph, path: str): def _save_image(g: Digraph, path: str, fmt: str): - with tempfile.TemporaryDirectory() as tmpdir: - with tempfile.NamedTemporaryFile(dir=tmpdir) as tmpfile: - svg_file = g.render(tmpfile.name, format=fmt) - shutil.copy(svg_file, path) + g.render(path, format=fmt) _IMAGE_FMTS = {'svg', 'png'}