From 41a2f90a7d8a0998b2c124f6e08fa4ba10cba575 Mon Sep 17 00:00:00 2001 From: robinw0928 <104830875+robinw0928@users.noreply.github.com> Date: Tue, 16 Aug 2022 13:36:48 +0800 Subject: [PATCH] Enhance two integration tests (#511) * Enhance two integration tests Enhance test_append and test_array_creation 1. add negative tests 2. add more test cases 3. refactor test code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address comments 1. Create test class for negative testing 2. Refactor out test functions 3. Use parameterize * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address comments - part2 1. update run_test name to check_array_method 2. use parameterize for step zero cases of arange * Address comments - Part 3 1. add pytest.mark.xfail for cases with expected failure 2. Small Fix: replace Assert with raising ValueError in deferred.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address comments - fix a typo Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- cunumeric/deferred.py | 9 +- tests/integration/test_append.py | 88 ++++----- tests/integration/test_array_creation.py | 227 +++++++++++++++++------ tests/integration/utils/utils.py | 55 ++++++ 4 files changed, 280 insertions(+), 99 deletions(-) create mode 100644 tests/integration/utils/utils.py diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index c99c1c9e8..98ea9026a 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -708,7 +708,11 @@ def _broadcast(self, shape: NdShape) -> Any: for dim in range(len(shape)): if result.shape[dim] != shape[dim]: - assert result.shape[dim] == 1 + if result.shape[dim] != 1: + raise ValueError( + f"Shape did not match along dimension {dim} " + "and the value is not equal to 1" + ) result = result.project(dim, 0).promote(dim, shape[dim]) return result @@ -1235,7 +1239,8 @@ def _fill(self, value: Any) -> None: def fill(self, numpy_array: Any) -> None: assert isinstance(numpy_array, np.ndarray) - assert numpy_array.size == 1 + if numpy_array.size != 1: + raise ValueError("Filled value array size is not equal to 1") assert self.dtype == numpy_array.dtype # Have to copy the numpy array because this launch is asynchronous # and we need to make sure the application doesn't mutate the value diff --git a/tests/integration/test_append.py b/tests/integration/test_append.py index 09c3d73c3..222ae632a 100644 --- a/tests/integration/test_append.py +++ b/tests/integration/test_append.py @@ -15,42 +15,10 @@ import numpy as np import pytest +from utils.utils import check_array_method import cunumeric as num - -def _run_test(arr, values, test_args): - for axis in test_args: - b = np.append(arr, values, axis) - c = num.append(arr, values, axis) - is_equal = True - err_arr = [b, c] - - if len(b) != len(c): - is_equal = False - err_arr = [b, c] - else: - for each in zip(b, c): - if not np.array_equal(*each): - err_arr = each - is_equal = False - break - print_msg = ( - f"np.append(array({arr.shape}), array({values.shape}), {axis})" - ) - assert is_equal, ( - f"Failed, {print_msg}\n" - f"numpy result: {err_arr[0]}, {b.shape}\n" - f"cunumeric_result: {err_arr[1]}, {c.shape}\n" - f"cunumeric and numpy shows" - f" different result\n" - ) - print( - f"Passed, {print_msg}, np: ({b.shape}, {b.dtype})" - f", cunumeric: ({c.shape}, {c.dtype}" - ) - - DIM = 10 # test append w/ 1D, 2D and 3D arrays @@ -61,6 +29,7 @@ def _run_test(arr, values, test_args): (1, 1), (1, 1, 1), (1, DIM), + (1, DIM, 1), (DIM, DIM), (DIM, DIM, DIM), ] @@ -69,16 +38,51 @@ def _run_test(arr, values, test_args): @pytest.mark.parametrize("size", SIZES, ids=str) def test_append(size): a = np.random.randint(low=0, high=100, size=size) + test_args = [-1] + list(range(a.ndim)) - test_args = list(range(a.ndim)) + [None] - - # test the exception for 1D array on append - _run_test(a, a, test_args) - - if a.ndim > 1: - # 1D array - b = np.random.randint(low=0, high=100, size=(DIM,)) - _run_test(a, b, [None]) + for axis in test_args: + size_b = list(size) + size_b[axis] = size[axis] + 10 + b = np.random.randint(low=0, high=100, size=size_b) + print_msg = f"np.append(array({a.shape}), array({b.shape}), {axis})" + check_array_method("append", [a, b], {"axis": axis}, print_msg) + + +@pytest.mark.parametrize("size_b", SIZES, ids=str) +@pytest.mark.parametrize("size_a", SIZES, ids=str) +def test_append_axis_none(size_a, size_b): + axis = None + a = np.random.randint(low=0, high=100, size=size_a) + b = np.random.randint(low=0, high=100, size=size_b) + print_msg = f"np.append(array({a.shape}), array({b.shape}), {axis})" + check_array_method("append", [a, b], {"axis": axis}, print_msg) + + +class TestAppendErrors: + def setup(self): + size_a = (1, DIM) + self.a = np.random.randint(low=0, high=100, size=size_a) + + def test_bad_dimension(self): + size_b = (1, DIM, 1) + b = np.random.randint(low=0, high=100, size=size_b) + + msg = ( + "All arguments to concatenate must have the " + "same number of dimensions" + ) + with pytest.raises(ValueError, match=msg): + num.append(self.a, b, axis=1) + + def test_bad_index(self): + with pytest.raises(IndexError): + num.append(self.a, self.a, axis=5) + + def test_bad_shape(self): + size_c = (10, DIM) + c = np.random.randint(low=0, high=100, size=size_c) + with pytest.raises(ValueError): + num.append(self.a, c, axis=1) if __name__ == "__main__": diff --git a/tests/integration/test_array_creation.py b/tests/integration/test_array_creation.py index 821944c10..6054a5759 100644 --- a/tests/integration/test_array_creation.py +++ b/tests/integration/test_array_creation.py @@ -13,6 +13,8 @@ # limitations under the License. # +from itertools import product + import numpy as np import pytest @@ -32,80 +34,164 @@ def test_array(): assert x.dtype == y.dtype +CREATION_FUNCTIONS = ("zeros", "ones") +FILLED_VALUES = [0, 1, 1000, 123.456] +SIZES = (0, 1, 2) +NDIMS = 5 +DTYPES = (np.uint32, np.int32, np.float64, np.complex128) + + def test_empty(): - xe = num.empty((2, 3)) - ye = np.empty((2, 3)) - assert xe.shape == ye.shape - assert xe.dtype == ye.dtype + par = (SIZES, range(NDIMS), DTYPES) + for size, ndims, dtype in product(*par): + shape = ndims * [size] + xf = num.empty(shape, dtype=dtype) + yf = np.empty(shape, dtype=dtype) -def test_zeros(): - xz = num.zeros((2, 3)) - yz = np.zeros((2, 3)) - assert np.array_equal(xz, yz) - assert xz.dtype == yz.dtype + assert xf.shape == yf.shape + assert xf.dtype == yf.dtype -def test_ones(): - xo = num.ones((2, 3)) - yo = np.ones((2, 3)) - assert np.array_equal(xo, yo) - assert xo.dtype == yo.dtype +@pytest.mark.parametrize("fn", CREATION_FUNCTIONS) +def test_creation_func(fn): + num_f = getattr(num, fn) + np_f = getattr(np, fn) + par = (SIZES, range(NDIMS), DTYPES) + for size, ndims, dtype in product(*par): + shape = ndims * [size] -def test_full(): - xf = num.full((2, 3), 3) - yf = np.full((2, 3), 3) - assert np.array_equal(xf, yf) - assert xf.dtype == yf.dtype + xf = num_f(shape, dtype=dtype) + yf = np_f(shape, dtype=dtype) + assert np.array_equal(xf, yf) + assert xf.dtype == yf.dtype -def test_empty_like(): - x = num.array([1, 2, 3]) - y = num.array(x) - xel = num.empty_like(x) - yel = np.empty_like(y) - assert xel.shape == yel.shape - assert xel.dtype == yel.dtype +@pytest.mark.parametrize("value", FILLED_VALUES) +def test_full(value): + par = (SIZES, range(NDIMS), DTYPES) + for size, ndims, dtype in product(*par): + shape = ndims * [size] -def test_zeros_like(): - x = num.array([1, 2, 3]) - y = num.array(x) - xzl = num.zeros_like(x) - yzl = np.zeros_like(y) - assert np.array_equal(xzl, yzl) - assert xzl.dtype == yzl.dtype + xf = num.full(shape, value, dtype=dtype) + yf = np.full(shape, value, dtype=dtype) + assert np.array_equal(xf, yf) + assert xf.dtype == yf.dtype -def test_ones_like(): - x = num.array([1, 2, 3]) - y = num.array(x) - xol = num.ones_like(x) - yol = np.ones_like(y) - assert np.array_equal(xol, yol) - assert xol.dtype == yol.dtype +SHAPES_NEGATIVE = [ + -1, + (-1, 2, 3), + np.array([2, -3, 4]), +] -def test_full_like(): - x = num.array([1, 2, 3]) - y = num.array(x) - xfl = num.full_like(x, 3) - yfl = np.full_like(y, 3) + +class TestCreationErrors: + def setup(self): + self.bad_type_shape = (2, 3.0) + + @pytest.mark.parametrize("shape", SHAPES_NEGATIVE, ids=str) + class TestNegativeShape: + @pytest.mark.parametrize("fn", ("empty", "zeros", "ones")) + def test_creation(self, shape, fn): + with pytest.raises(ValueError): + getattr(num, fn)(shape) + + def test_full(self, shape): + with pytest.raises(ValueError): + num.full(shape, 10) + + @pytest.mark.parametrize("fn", ("empty", "zeros", "ones")) + def test_creation_bad_type(self, fn): + with pytest.raises(TypeError): + getattr(num, fn)(self.bad_type_shape) + + def test_full_bad_type(self): + with pytest.raises(TypeError): + num.full(self.bad_type_shape, 10) + + # additional special case for full + def test_full_bad_filled_value(self): + with pytest.raises(ValueError): + num.full((2, 3), [10, 20, 30]) + + +DATA_ARGS = [ + # Array scalars + (np.array(3.0), None), + (np.array(3), "f8"), + # 1D arrays + (np.array([]), None), + (np.arange(6, dtype="f4"), None), + (np.arange(6), "c16"), + # 2D arrays + (np.array([[]]), None), + (np.arange(6).reshape(2, 3), None), + (np.arange(6).reshape(3, 2), "i1"), + # 3D arrays + (np.array([[[]]]), None), + (np.arange(24).reshape(2, 3, 4), None), + (np.arange(24).reshape(4, 3, 2), "f4"), +] +LIKE_FUNCTIONS = ("zeros_like", "ones_like") + + +@pytest.mark.parametrize("x_np,dtype", DATA_ARGS) +def test_empty_like(x_np, dtype): + x = num.array(x_np) + xfl = num.empty_like(x, dtype=dtype) + yfl = np.empty_like(x_np, dtype=dtype) + + assert xfl.shape == yfl.shape + assert xfl.dtype == yfl.dtype + + +@pytest.mark.parametrize("x_np,dtype", DATA_ARGS) +@pytest.mark.parametrize("fn", LIKE_FUNCTIONS) +def test_func_like(fn, x_np, dtype): + num_f = getattr(num, fn) + np_f = getattr(np, fn) + + x = num.array(x_np) + xfl = num_f(x, dtype=dtype) + yfl = np_f(x_np, dtype=dtype) + + assert np.array_equal(xfl, yfl) + assert xfl.dtype == yfl.dtype + + +@pytest.mark.parametrize("value", FILLED_VALUES) +@pytest.mark.parametrize("x_np, dtype", DATA_ARGS) +def test_full_like(x_np, dtype, value): + x = num.array(x_np) + + xfl = num.full_like(x, value, dtype=dtype) + yfl = np.full_like(x_np, value, dtype=dtype) assert np.array_equal(xfl, yfl) assert xfl.dtype == yfl.dtype - # xfls = num.full_like(x, '3', dtype=np.str_) - # yfls = np.full_like(y, '3', dtype=np.str_) - # assert(num.array_equal(xfls, yfls)) - # assert(xfls.dtype == yfls.dtype) + +def test_full_like_bad_filled_value(): + x = num.array([[1, 2, 3], [4, 5, 6]]) + with pytest.raises(ValueError): + num.full_like(x, [10, 20, 30]) ARANGE_ARGS = [ - (1,), + (0,), (10,), - (2.0, 10.0), - (2, 30, 3), + (3.5,), + pytest.param((-10), marks=pytest.mark.xfail), + (2, 10), + pytest.param((2, -10), marks=pytest.mark.xfail), + (-2.5, 10.0), + pytest.param((1, -10, -2.5), marks=pytest.mark.xfail), + (1.0, -10.0, -2.5), + (-10, 10, 10), + (-10, 10, -100), ] @@ -117,13 +203,44 @@ def test_arange(args): assert x.dtype == y.dtype -def test_arange_with_dtype(): - x = num.arange(10, dtype=np.int32) - y = np.arange(10, dtype=np.int32) +@pytest.mark.parametrize("dtype", [np.int32, np.float64], ids=str) +@pytest.mark.parametrize("args", ARANGE_ARGS, ids=str) +def test_arange_with_dtype(args, dtype): + x = num.arange(*args, dtype=dtype) + y = np.arange(*args, dtype=dtype) assert np.array_equal(x, y) assert x.dtype == y.dtype +ARANGE_ARGS_STEP_ZERO = [ + (0, 0, 0), + (0, 10, 0), + (-10, 10, 0), + (1, 10, 0), + (10, -10, 0), + (0.0, 0.0, 0.0), + (0.0, 10.0, 0.0), + (-10.0, 10.0, 0.0), + (1.0, 10.0, 0.0), + (10.0, -10.0, 0.0), +] + + +class TestArrangeErrors: + def test_inf(self): + with pytest.raises(OverflowError): + num.arange(0, num.inf) + + def test_nan(self): + with pytest.raises(ValueError): + num.arange(0, 1, num.nan) + + @pytest.mark.parametrize("args", ARANGE_ARGS_STEP_ZERO, ids=str) + def test_zero_division(self, args): + with pytest.raises(ZeroDivisionError): + num.arange(*args) + + def test_zero_with_nd_ndarray_shape(): shape = num.array([2, 3, 4]) x = num.zeros(shape) diff --git a/tests/integration/utils/utils.py b/tests/integration/utils/utils.py new file mode 100644 index 000000000..98bff79c6 --- /dev/null +++ b/tests/integration/utils/utils.py @@ -0,0 +1,55 @@ +# Copyright 2021-2022 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np + +import cunumeric as num + + +def compare_array(a, b): + """ + Compare two array using zip method. + """ + + if len(a) != len(b): + return False, [a, b] + else: + for each in zip(a, b): + if not np.array_equal(*each): + return False, each + return True, None + + +def check_array_method(fn, args, kwargs, print_msg): + """ + Run np.func and num.func respectively and compare results + """ + + a = getattr(num, fn)(*args, **kwargs) + b = getattr(np, fn)(*args, **kwargs) + + is_equal, err_arr = compare_array(a, b) + + assert is_equal, ( + f"Failed, {print_msg}\n" + f"numpy result: {err_arr[0]}, {a.shape}\n" + f"cunumeric_result: {err_arr[1]}, {b.shape}\n" + f"cunumeric and numpy shows" + f" different result\n" + ) + print( + f"Passed, {print_msg}, np: ({a.shape}, {a.dtype})" + f", cunumeric: ({b.shape}, {b.dtype})" + )