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

TEST: Run all tests through pytest #2090

Merged
merged 26 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
075e016
run legacy tests with pytest
david-cortes-intel Oct 7, 2024
c5e3f4b
add back pyargs to pytest call
david-cortes-intel Oct 7, 2024
3ba6819
more pyargs
david-cortes-intel Oct 7, 2024
a2e1e70
try changing order of PYTHONPATH
david-cortes-intel Oct 7, 2024
1026e4c
try changing to relative imports instead of adding PYTHONPATH
david-cortes-intel Oct 8, 2024
6e421b7
missing delete
david-cortes-intel Oct 8, 2024
639d0fd
try executing non-mpi tests first to see if they also fail
david-cortes-intel Oct 8, 2024
b295a44
try deleting __init__.py in test folder
david-cortes-intel Oct 8, 2024
c56f5bd
try removing relative import
david-cortes-intel Oct 8, 2024
6ce5300
move patching test also to pytest, restore MPI on windows
david-cortes-intel Oct 8, 2024
a8c3009
Merge branch 'main' into pytest
david-cortes-intel Oct 9, 2024
02caa5b
fix unexecuted parametrization
david-cortes-intel Oct 10, 2024
de145cc
remove unused import
david-cortes-intel Oct 10, 2024
accad99
better naming
david-cortes-intel Oct 10, 2024
6c7a161
add name of estimator being patched
david-cortes-intel Oct 10, 2024
a30eb50
account for platforms using non-standard process codes
david-cortes-intel Oct 10, 2024
a209abb
correct fixture names
david-cortes-intel Oct 10, 2024
aa22bc5
use yield instead of finalize
david-cortes-intel Oct 10, 2024
5bb9c50
try removing pyargs to see what happens
david-cortes-intel Oct 10, 2024
cac58b1
add separate tests for global and module-specific
david-cortes-intel Oct 10, 2024
aa57ba0
remove unused argument
david-cortes-intel Oct 10, 2024
5653e3f
remove global patching test
david-cortes-intel Oct 10, 2024
fc09a8e
fixes for windows
david-cortes-intel Oct 10, 2024
ef03240
try changing fixtures back to manual finalizers
david-cortes-intel Oct 10, 2024
d4a06eb
add comment about the yield fixtures
david-cortes-intel Oct 11, 2024
5a6e450
Update .ci/scripts/test_global_patch.py
david-cortes-intel Oct 14, 2024
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
157 changes: 119 additions & 38 deletions .ci/scripts/test_global_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,136 @@
# limitations under the License.
# ===============================================================================

import os
import subprocess
import sys

# test patching from command line
err_code = subprocess.call(
[sys.executable, "-m", "sklearnex.glob", "patch_sklearn", "-a", "svc"]
)
assert not err_code
from sklearn.svm import SVC, SVR
import pytest

assert SVC.__module__.startswith("daal4py") or SVC.__module__.startswith("sklearnex")
assert not SVR.__module__.startswith("daal4py") and not SVR.__module__.startswith(
"sklearnex"
)
# This is a workaround for older versions of Python on Windows
# which didn't have it as part of the built-in 'os' module.
EX_OK = os.EX_OK if hasattr(os, "EX_OK") else 0

# Note: from the structure of this file, one might think of adding a test

from sklearnex import patch_sklearn, unpatch_sklearn
# along the lines of 'test_patching_all_from_command_line'. There is however
# an issue in that, after the first time a scikit-learn module is imported,
# further calls to 'patch_sklearn' with different arguments will have no effect
# since sklearn is already imported. Reloading it through 'importlib.reload'
# or deleting it from 'sys.modules' doesn't appear to have the intended effect
# either. This also makes this first command-line fixture and tests that use
# it not entirely idempotent, given that they need to import sklearn modules.

# test unpatching from command line
err_code = subprocess.call([sys.executable, "-m", "sklearnex.glob", "unpatch_sklearn"])
assert not err_code
unpatch_sklearn()
Comment on lines -37 to -39
Copy link
Contributor

@icfaust icfaust Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state of the unpatch_sklearn() test was previously after a subprocess.call([sys.executable, "-m", "sklearnex.glob", "unpatch_sklearn"]) not a
subprocess.call(
[sys.executable, "-m", "sklearnex.glob", "patch_sklearn", "-a", "svc"] call, meaning the state of the test is different in the new implementation. This is okay if this was what you desired

#2090 (comment)

Copy link
Contributor Author

@david-cortes-intel david-cortes-intel Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I had missed that detail entirely.

So I tried adding a new test for global state separately from just SVC, but the test actually ends up failing:

@pytest.fixture
def patch_all_from_command_line():
    err_code = subprocess.call(
        [sys.executable, "-m", "sklearnex.glob", "patch_sklearn"]
    )
    assert err_code == os.EX_OK

    yield

    err_code = subprocess.call(
        [sys.executable, "-m", "sklearnex.glob", "unpatch_sklearn"]
    )
    assert err_code == os.EX_OK

def test_patching_all_from_command_line(patch_all_from_command_line):
    from sklearn.svm import SVC, SVR

    assert SVC.__module__.startswith("daal4py")
    assert SVC.__module__.startswith("sklearnex")
    assert SVR.__module__.startswith("daal4py")
    assert SVR.__module__.startswith("sklearnex")

should that be expected to succeed? same happens if checking them with "or", and happens for both SVC and SVR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that its failing on
assert SVC.__module__.startswith("daal4py"). You can remove
assert SVC.__module__.startswith("daal4py") and assert SVR.__module__.startswith("daal4py")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also fails for sklearnex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it this new test as an extra commit here, let's see if it also fails in the CI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a legit failure then, lets see

Copy link
Contributor Author

@david-cortes-intel david-cortes-intel Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it also fails in the CI jobs:

    def test_patching_all_from_command_line(patch_all_from_command_line):
        from sklearn.svm import SVC, SVR
    
>       assert SVC.__module__.startswith("daal4py") or SVC.__module__.startswith("sklearnex")
E       AssertionError: assert (False or False)
E        +  where False = <built-in method startswith of str object at 0x7f8679a04300>('daal4py')
E        +    where <built-in method startswith of str object at 0x7f8679a04300> = 'sklearn.svm._classes'.startswith
E        +      where 'sklearn.svm._classes' = <class 'sklearn.svm._classes.SVC'>.__module__
E        +  and   False = <built-in method startswith of str object at 0x7f8679a04300>('sklearnex')
E        +    where <built-in method startswith of str object at 0x7f8679a04300> = 'sklearn.svm._classes'.startswith
E        +      where 'sklearn.svm._classes' = <class 'sklearn.svm._classes.SVC'>.__module__

scripts/test_global_patch.py:83: AssertionError

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is bad, i'll admit I don't know much about the glob use. I wonder if sklearn has been pre-imported via sklearnex imports, and the change in glob not affecting it. There is definitely something here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, looks like that does indeed make a difference. If leaving it as the only test then it actually succeeds. Will try to find a workaround.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't manage to find any solution so in the end I just removed the test and left a comment in the file.

from sklearn.svm import SVC, SVR
# Note 2: don't try to change these into 'yield' fixtures, because otherwise
# some test runners on windows which use multi-processing will throw errors
# about the fixtures not being serializable.

assert not SVC.__module__.startswith("daal4py") and not SVC.__module__.startswith(
"sklearnex"
)
assert not SVR.__module__.startswith("daal4py") and not SVR.__module__.startswith(
"sklearnex"
)

@pytest.fixture
def patch_svc_from_command_line(request):
err_code = subprocess.call(
[sys.executable, "-m", "sklearnex.glob", "patch_sklearn", "-a", "svc"]
)
assert err_code == EX_OK

# test patching from function
patch_sklearn(name=["svc"], global_patch=True)
from sklearn.svm import SVC, SVR
def finalizer():
err_code = subprocess.call(
[sys.executable, "-m", "sklearnex.glob", "unpatch_sklearn", "-a", "svc"]
)
assert err_code == EX_OK

assert SVC.__module__.startswith("daal4py") or SVC.__module__.startswith("sklearnex")
assert not SVR.__module__.startswith("daal4py") and not SVR.__module__.startswith(
"sklearnex"
)
request.addfinalizer(finalizer)
return


# test unpatching from function
unpatch_sklearn(global_unpatch=True)
from sklearn.svm import SVC, SVR
def test_patching_svc_from_command_line(patch_svc_from_command_line):
from sklearn.svm import SVC, SVR

assert not SVC.__module__.startswith("daal4py") and not SVC.__module__.startswith(
"sklearnex"
)
assert not SVR.__module__.startswith("daal4py") and not SVR.__module__.startswith(
"sklearnex"
)
assert SVC.__module__.startswith("daal4py") or SVC.__module__.startswith("sklearnex")
assert not SVR.__module__.startswith("daal4py") and not SVR.__module__.startswith(
"sklearnex"
)


def test_unpatching_svc_from_command_line(patch_svc_from_command_line):
err_code = subprocess.call(
[sys.executable, "-m", "sklearnex.glob", "unpatch_sklearn"]
)
assert err_code == EX_OK
from sklearnex import unpatch_sklearn

unpatch_sklearn()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/intel/scikit-learn-intelex/blob/main/sklearnex/tests/test_monkeypatch.py#L65 Just be careful with these functions since they impact global state and aren't local to the test (anything with a patch_sklearn call should be with a 'try-finally' with an unpatch_sklearn)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the idea behind the fixtures with finalizers.

from sklearn.svm import SVC, SVR

assert not SVC.__module__.startswith("daal4py") and not SVC.__module__.startswith(
"sklearnex"
)
assert not SVR.__module__.startswith("daal4py") and not SVR.__module__.startswith(
"sklearnex"
)


@pytest.fixture
def patch_svc_from_function(request):
from sklearnex import patch_sklearn, unpatch_sklearn

patch_sklearn(name=["svc"], global_patch=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again possibly yield?


def finalizer():
unpatch_sklearn(name=["svc"], global_unpatch=True)

request.addfinalizer(finalizer)
return


def test_patching_svc_from_function(patch_svc_from_function):
from sklearn.svm import SVC, SVR

assert SVC.__module__.startswith("daal4py") or SVC.__module__.startswith("sklearnex")
assert not SVR.__module__.startswith("daal4py") and not SVR.__module__.startswith(
"sklearnex"
)


def test_unpatching_svc_from_function(patch_svc_from_function):
from sklearnex import unpatch_sklearn

unpatch_sklearn(global_unpatch=True)
from sklearn.svm import SVC, SVR

assert not SVC.__module__.startswith("daal4py")
assert not SVC.__module__.startswith("sklearnex")
assert not SVR.__module__.startswith("daal4py")
assert not SVR.__module__.startswith("sklearnex")


@pytest.fixture
def patch_all_from_function(request):
from sklearnex import patch_sklearn, unpatch_sklearn

patch_sklearn(global_patch=True)

def finalizer():
unpatch_sklearn(global_unpatch=True)

request.addfinalizer(finalizer)
return


def test_patching_svc_from_function(patch_all_from_function):
from sklearn.svm import SVC, SVR

assert SVC.__module__.startswith("daal4py") or SVC.__module__.startswith("sklearnex")
assert SVR.__module__.startswith("daal4py") or SVR.__module__.startswith("sklearnex")


def test_unpatching_all_from_function(patch_all_from_function):
from sklearnex import unpatch_sklearn

unpatch_sklearn(global_unpatch=True)
from sklearn.svm import SVC, SVR

assert not SVC.__module__.startswith("daal4py")
assert not SVC.__module__.startswith("sklearnex")
assert not SVR.__module__.startswith("daal4py")
assert not SVR.__module__.startswith("sklearnex")
6 changes: 3 additions & 3 deletions conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ test:
- daal4py/sklearn
commands:
- python -c "import daal4py"
- mpirun -n 4 python -m unittest discover -v -s tests -p test*spmd*.py # [not win]
- mpiexec -localonly -n 4 python -m unittest discover -v -s tests -p test*spmd*.py # [win]
- python -m unittest discover -v -s tests -p test*.py
- mpirun -n 4 pytest --verbose -s tests/test*spmd*.py # [not win]
- mpiexec -localonly -n 4 pytest --verbose -s tests/test*spmd*.py # [win]
- pytest --pyargs --verbose -s tests
- pytest --pyargs daal4py/sklearn/
- python tests/run_examples.py

Expand Down
4 changes: 2 additions & 2 deletions conda-recipe/run_test.bat
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ IF DEFINED TBBROOT (
call "%TBBROOT%\env\vars.bat" || set exitcode=1
)

%PYTHON% -m unittest discover -v -s %1\tests -p test*.py || set exitcode=1
%PYTHON% -m pytest --verbose -s %1\tests || set exitcode=1

pytest --verbose --pyargs %1\daal4py\sklearn || set exitcode=1
pytest --verbose --pyargs sklearnex || set exitcode=1
pytest --verbose --pyargs %1\onedal --deselect="onedal/common/tests/test_policy.py" || set exitcode=1
python %1\.ci\scripts\test_global_patch.py || set exitcode=1
pytest --verbose %1\.ci\scripts\test_global_patch.py || set exitcode=1
EXIT /B %exitcode%
15 changes: 8 additions & 7 deletions conda-recipe/run_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,19 @@ return_code=0
python -c "import daal4py"
return_code=$(($return_code + $?))

echo "Pytest run of legacy unittest ..."
echo ${daal4py_dir}
pytest --verbose -s ${daal4py_dir}/tests
return_code=$(($return_code + $?))

echo "NO_DIST=$NO_DIST"
if [[ ! $NO_DIST ]]; then
echo "MPI unittest discover testing ..."
echo "MPI pytest run of legacy unittest ..."
mpirun --version
mpirun -n 4 python -m unittest discover -v -s ${daal4py_dir}/tests -p test*spmd*.py
mpirun -n 4 pytest --verbose -s ${daal4py_dir}/tests/test*spmd*.py
return_code=$(($return_code + $?))
fi

echo "Unittest discover testing ..."
python -m unittest discover -v -s ${daal4py_dir}/tests -p test*.py
return_code=$(($return_code + $?))

echo "Pytest of daal4py running ..."
pytest --verbose --pyargs ${daal4py_dir}/daal4py/sklearn
return_code=$(($return_code + $?))
Expand All @@ -62,7 +63,7 @@ pytest --verbose --pyargs ${daal4py_dir}/onedal
return_code=$(($return_code + $?))

echo "Global patching test running ..."
python ${daal4py_dir}/.ci/scripts/test_global_patch.py
pytest --verbose -s ${daal4py_dir}/.ci/scripts/test_global_patch.py
return_code=$(($return_code + $?))

exit $return_code
Empty file removed tests/__init__.py
Empty file.
77 changes: 28 additions & 49 deletions tests/test_examples_sklearnex.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,59 +17,38 @@
import os
import subprocess
import sys
import unittest

from daal4py.sklearn._utils import get_daal_version
import pytest

test_path = os.path.abspath(os.path.dirname(__file__))
unittest_data_path = os.path.join(test_path, "unittest_data")
examples_path = os.path.join(os.path.dirname(test_path), "examples", "sklearnex")

print("Testing examples_sklearnex")
# First item is major version - 2021,
# second is minor+patch - 0110,
# third item is status - B
sklearnex_version = get_daal_version()
print("oneDAL version:", sklearnex_version)


class TestsklearnexExamples(unittest.TestCase):
"""Class for testing sklernex examples"""

# Get a list of all Python files in the examples directory
pass


def test_generator(file):
def testit(self):
# Run the script and capture its exit code
process = subprocess.run(
[sys.executable, os.path.join(examples_path, file)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
) # nosec
exit_code = process.returncode

# Assert that the exit code is 0
self.assertEqual(
exit_code,
0,
msg=f"Example has failed, the example's output:\n{process.stdout.decode()}\n{process.stderr.decode()}",
# This is a workaround for older versions of Python on Windows
# which didn't have it as part of the built-in 'os' module.
EX_OK = os.EX_OK if hasattr(os, "EX_OK") else 0


@pytest.mark.parametrize(
"file",
[
f
for f in os.listdir(examples_path)
if f.endswith(".py") and "spmd" not in f and "dpnp" not in f and "dpctl" not in f
],
)
def test_sklearn_example(file):
# Run the script and capture its exit code
process = subprocess.run(
[sys.executable, os.path.join(examples_path, file)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
) # nosec
exit_code = process.returncode

if exit_code != EX_OK:
pytest.fail(
pytrace=False,
reason=f"Example has failed, the example's output:\n{process.stdout.decode()}\n{process.stderr.decode()}",
)

setattr(TestsklearnexExamples, "test_" + os.path.splitext(file)[0], testit)
print("Generating tests for " + os.path.splitext(file)[0])


files = [
f
for f in os.listdir(examples_path)
if f.endswith(".py") and "spmd" not in f and "dpnp" not in f and "dpctl" not in f
]

for file in files:
test_generator(file)

if __name__ == "__main__":
unittest.main()
Loading