diff --git a/tests/test_logical.py b/tests/test_logical.py
new file mode 100644
index 0000000..b9e8db2
--- /dev/null
+++ b/tests/test_logical.py
@@ -0,0 +1,536 @@
+import random
+
+import pytest
+
+import arrayfire_wrapper.dtypes as dtype
+import arrayfire_wrapper.lib as wrapper
+from tests.utility_functions import check_type_supported, get_all_types, get_real_types
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_and_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test and_ operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.and_(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_and_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test and_ operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+
+        result = wrapper.and_(lhs, rhs)
+
+        assert (
+            wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+        ), f"failed for shape: {shape} and dtype {invdtypes}"
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_real_types())
+def test_bitand_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test bitand operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    if dtype_name == dtype.f16 or dtype_name == dtype.f32:
+        pytest.skip()
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.bitand(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_bitand_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test bitand operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.bitand(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_real_types())
+def test_bitnot_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test bitnot operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    if dtype_name == dtype.f16 or dtype_name == dtype.f32:
+        pytest.skip()
+    out = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.bitnot(out)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_bitnot_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test bitnot operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        out = wrapper.randu(shape, invdtypes)
+        wrapper.bitnot(out)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_real_types())
+def test_bitor_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test bitor operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    if dtype_name == dtype.f16 or dtype_name == dtype.f32:
+        pytest.skip()
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.bitor(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_bitor_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test bitor operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.bitor(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_real_types())
+def test_bitxor_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test bitxor operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    if dtype_name == dtype.f16 or dtype_name == dtype.f32:
+        pytest.skip()
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.bitxor(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_bitxor_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test bitxor operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.bitxor(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_eq_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test eq operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.eq(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_eq_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test eq operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.eq(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_ge_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test >= operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.ge(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_ge_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test >= operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.ge(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_gt_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test > operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.gt(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_gt_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test > operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.gt(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_le_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test <= operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.le(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_le_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test <= operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.le(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_lt_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test < operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.lt(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_lt_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test < operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.lt(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_neq_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test not equal operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.neq(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_neq_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test neq operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.neq(lhs, rhs)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_not_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test not operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    out = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.not_(out)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_not_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test not operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        out = wrapper.randu(shape, invdtypes)
+        wrapper.not_(out)
+
+
+@pytest.mark.parametrize(
+    "shape",
+    [
+        (),
+        (random.randint(1, 10),),
+        (random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+        (random.randint(1, 10), random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)),
+    ],
+)
+@pytest.mark.parametrize("dtype_name", get_all_types())
+def test_or_shape_dtypes(shape: tuple, dtype_name: dtype.Dtype) -> None:
+    """Test or operation between two arrays of the same shape"""
+    check_type_supported(dtype_name)
+    lhs = wrapper.randu(shape, dtype_name)
+    rhs = wrapper.randu(shape, dtype_name)
+
+    result = wrapper.or_(lhs, rhs)
+
+    assert (
+        wrapper.get_dims(result)[0 : len(shape)] == shape  # noqa
+    ), f"failed for shape: {shape} and dtype {dtype_name}"
+
+
+@pytest.mark.parametrize(
+    "invdtypes",
+    [
+        dtype.c64,
+        dtype.f64,
+    ],
+)
+def test_or_shapes_invalid(invdtypes: dtype.Dtype) -> None:
+    """Test or operation between two arrays of the same shape"""
+    with pytest.raises(RuntimeError):
+        shape = (3, 3)
+        lhs = wrapper.randu(shape, invdtypes)
+        rhs = wrapper.randu(shape, invdtypes)
+        wrapper.or_(lhs, rhs)