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

ENH, CI: Add Emscripten to CI #21895

Merged
merged 4 commits into from Nov 15, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/emscripten.yml
@@ -0,0 +1,75 @@
# To enable this workflow on a fork, comment out:
#
# if: github.repository == 'numpy/numpy'
rgommers marked this conversation as resolved.
Show resolved Hide resolved
name: Test Emscripten/Pyodide build

on:
pull_request:
branches:
- main
- maintenance/**

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read # to fetch code (actions/checkout)

jobs:
build-wasm-emscripten:
runs-on: ubuntu-latest
rgommers marked this conversation as resolved.
Show resolved Hide resolved
if: github.repository == 'numpy/numpy'
env:
PYODIDE_VERSION: 0.22.0a3
# PYTHON_VERSION and EMSCRIPTEN_VERSION are determined by PYODIDE_VERSION.
# The appropriate versions can be found in the Pyodide repodata.json
# "info" field, or in Makefile.envs:
# https://github.com/pyodide/pyodide/blob/main/Makefile.envs#L2
PYTHON_VERSION: 3.10.2
EMSCRIPTEN_VERSION: 3.1.24
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
NODE_VERSION: 18
steps:
- name: Checkout numpy
uses: actions/checkout@v3
with:
submodules: true
# versioneer.py requires the latest tag to be reachable. Here we
# fetch the complete history to get access to the tags.
# A shallow clone can work when the following issue is resolved:
# https://github.com/actions/checkout/issues/338
fetch-depth: 0

- name: set up python
id: setup-python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}

- uses: mymindstorm/setup-emsdk@v11
with:
version: ${{ env.EMSCRIPTEN_VERSION }}
actions-cache-folder: emsdk-cache

- name: Install pyodide-build
run: pip install pyodide-build==$PYODIDE_VERSION

- name: Build
run: CFLAGS=-g2 LDFLAGS=-g2 pyodide build

- name: set up node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}

- name: Set up Pyodide virtual environment
run: |
pyodide venv .venv-pyodide
source .venv-pyodide/bin/activate
pip install dist/*.whl
pip install -r test_requirements.txt
- name: Test
run: |
source .venv-pyodide/bin/activate
cd ..
python numpy/runtests.py -n -vv
12 changes: 12 additions & 0 deletions numpy/core/feature_detection_cmath.h
Expand Up @@ -10,6 +10,18 @@
#define cldouble complex long double
#endif

// musl libc defines the following macros which breaks the declarations.
// See https://git.musl-libc.org/cgit/musl/tree/include/complex.h#n108
#undef crealf
#undef cimagf
#undef conjf
#undef creal
#undef cimag
#undef conj
#undef creall
#undef cimagl
#undef cconjl

cfloat csinf(cfloat);
cfloat ccosf(cfloat);
cfloat ctanf(cfloat);
Expand Down
3 changes: 2 additions & 1 deletion numpy/core/tests/test_casting_floatingpoint_errors.py
@@ -1,6 +1,6 @@
import pytest
from pytest import param

from numpy.testing import IS_WASM
import numpy as np


Expand Down Expand Up @@ -136,6 +136,7 @@ def flat_assignment():

yield flat_assignment

@pytest.mark.skipif(IS_WASM, reason="no wasm fp exception support")
@pytest.mark.parametrize(["value", "dtype"], values_and_dtypes())
@pytest.mark.filterwarnings("ignore::numpy.ComplexWarning")
def test_floatingpoint_errors_casting(dtype, value):
Expand Down
3 changes: 3 additions & 0 deletions numpy/core/tests/test_cython.py
Expand Up @@ -5,6 +5,7 @@
import pytest

import numpy as np
from numpy.testing import IS_WASM

# This import is copied from random.tests.test_extending
try:
Expand All @@ -30,6 +31,8 @@
@pytest.fixture
def install_temp(request, tmp_path):
# Based in part on test_cython from random.tests.test_extending
if IS_WASM:
pytest.skip("No subprocess")

here = os.path.dirname(__file__)
ext_dir = os.path.join(here, "examples", "cython")
Expand Down
4 changes: 4 additions & 0 deletions numpy/core/tests/test_datetime.py
Expand Up @@ -4,6 +4,7 @@
import datetime
import pytest
from numpy.testing import (
IS_WASM,
assert_, assert_equal, assert_raises, assert_warns, suppress_warnings,
assert_raises_regex, assert_array_equal,
)
Expand Down Expand Up @@ -1294,6 +1295,7 @@ def check(a, b, res):
def test_timedelta_floor_divide(self, op1, op2, exp):
assert_equal(op1 // op2, exp)

@pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
@pytest.mark.parametrize("op1, op2", [
# div by 0
(np.timedelta64(10, 'us'),
Expand Down Expand Up @@ -1368,6 +1370,7 @@ def test_timedelta_divmod(self, op1, op2):
expected = (op1 // op2, op1 % op2)
assert_equal(divmod(op1, op2), expected)

@pytest.mark.skipif(IS_WASM, reason="does not work in wasm")
@pytest.mark.parametrize("op1, op2", [
# reuse cases from floordiv
# div by 0
Expand Down Expand Up @@ -1993,6 +1996,7 @@ def test_timedelta_modulus_error(self, val1, val2):
with assert_raises_regex(TypeError, "common metadata divisor"):
val1 % val2

@pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
def test_timedelta_modulus_div_by_zero(self):
with assert_warns(RuntimeWarning):
actual = np.timedelta64(10, 's') % np.timedelta64(0, 's')
Expand Down
4 changes: 3 additions & 1 deletion numpy/core/tests/test_errstate.py
Expand Up @@ -2,7 +2,7 @@
import sysconfig

import numpy as np
from numpy.testing import assert_, assert_raises
from numpy.testing import assert_, assert_raises, IS_WASM

# The floating point emulation on ARM EABI systems lacking a hardware FPU is
# known to be buggy. This is an attempt to identify these hosts. It may not
Expand All @@ -12,6 +12,7 @@
arm_softfloat = False if hosttype is None else hosttype.endswith('gnueabi')

class TestErrstate:
@pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
@pytest.mark.skipif(arm_softfloat,
reason='platform/cpu issue with FPU (gh-413,-15562)')
def test_invalid(self):
Expand All @@ -24,6 +25,7 @@ def test_invalid(self):
with assert_raises(FloatingPointError):
np.sqrt(a)

@pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
@pytest.mark.skipif(arm_softfloat,
reason='platform/cpu issue with FPU (gh-15562)')
def test_divide(self):
Expand Down
4 changes: 3 additions & 1 deletion numpy/core/tests/test_half.py
Expand Up @@ -3,7 +3,7 @@

import numpy as np
from numpy import uint16, float16, float32, float64
from numpy.testing import assert_, assert_equal, _OLD_PROMOTION
from numpy.testing import assert_, assert_equal, _OLD_PROMOTION, IS_WASM


def assert_raises_fpe(strmatch, callable, *args, **kwargs):
Expand Down Expand Up @@ -483,6 +483,8 @@ def test_half_coercion(self, weak_promotion):

@pytest.mark.skipif(platform.machine() == "armv5tel",
reason="See gh-413.")
@pytest.mark.skipif(IS_WASM,
reason="fp exceptions don't work in wasm.")
def test_half_fpe(self):
with np.errstate(all='raise'):
sx16 = np.array((1e-4,), dtype=float16)
Expand Down
3 changes: 2 additions & 1 deletion numpy/core/tests/test_indexing.py
Expand Up @@ -10,7 +10,7 @@
from itertools import product
from numpy.testing import (
assert_, assert_equal, assert_raises, assert_raises_regex,
assert_array_equal, assert_warns, HAS_REFCOUNT,
assert_array_equal, assert_warns, HAS_REFCOUNT, IS_WASM
)


Expand Down Expand Up @@ -563,6 +563,7 @@ def test_too_many_advanced_indices(self, index, num, original_ndim):
with pytest.raises(IndexError):
arr[(index,) * num] = 1.

@pytest.mark.skipif(IS_WASM, reason="no threading")
def test_structured_advanced_indexing(self):
# Test that copyswap(n) used by integer array indexing is threadsafe
# for structured datatypes, see gh-15387. This test can behave randomly.
Expand Down
3 changes: 3 additions & 0 deletions numpy/core/tests/test_limited_api.py
Expand Up @@ -5,7 +5,10 @@
import sysconfig
import pytest

from numpy.testing import IS_WASM


@pytest.mark.skipif(IS_WASM, reason="Can't start subprocess")
@pytest.mark.xfail(
sysconfig.get_config_var("Py_DEBUG"),
reason=(
Expand Down
4 changes: 3 additions & 1 deletion numpy/core/tests/test_mem_policy.py
Expand Up @@ -5,7 +5,7 @@
import numpy as np
import threading
import warnings
from numpy.testing import extbuild, assert_warns
from numpy.testing import extbuild, assert_warns, IS_WASM
import sys


Expand All @@ -18,6 +18,8 @@ def get_module(tmp_path):
"""
if sys.platform.startswith('cygwin'):
pytest.skip('link fails on cygwin')
if IS_WASM:
pytest.skip("Can't build module inside Wasm")
functions = [
("get_default_policy", "METH_NOARGS", """
Py_INCREF(PyDataMem_DefaultHandler);
Expand Down
3 changes: 2 additions & 1 deletion numpy/core/tests/test_nditer.py
Expand Up @@ -9,7 +9,7 @@
from numpy import array, arange, nditer, all
from numpy.testing import (
assert_, assert_equal, assert_array_equal, assert_raises,
HAS_REFCOUNT, suppress_warnings, break_cycles
IS_WASM, HAS_REFCOUNT, suppress_warnings, break_cycles
)


Expand Down Expand Up @@ -2025,6 +2025,7 @@ def test_buffered_cast_error_paths():
buf = next(it)
buf[...] = "a" # cannot be converted to int.

@pytest.mark.skipif(IS_WASM, reason="Cannot start subprocess")
@pytest.mark.skipif(not HAS_REFCOUNT, reason="PyPy seems to not hit this.")
def test_buffered_cast_error_paths_unraisable():
# The following gives an unraisable error. Pytest sometimes captures that
Expand Down
2 changes: 2 additions & 0 deletions numpy/core/tests/test_nep50_promotions.py
Expand Up @@ -9,6 +9,7 @@
import numpy as np

import pytest
from numpy.testing import IS_WASM


@pytest.fixture(scope="module", autouse=True)
Expand All @@ -19,6 +20,7 @@ def _weak_promotion_enabled():
np._set_promotion_state(state)


@pytest.mark.skipif(IS_WASM, reason="wasm doesn't have support for fp errors")
def test_nep50_examples():
with pytest.warns(UserWarning, match="result dtype changed"):
res = np.uint8(1) + 2
Expand Down
7 changes: 6 additions & 1 deletion numpy/core/tests/test_numeric.py
Expand Up @@ -12,7 +12,7 @@
from numpy.testing import (
assert_, assert_equal, assert_raises, assert_raises_regex,
assert_array_equal, assert_almost_equal, assert_array_almost_equal,
assert_warns, assert_array_max_ulp, HAS_REFCOUNT
assert_warns, assert_array_max_ulp, HAS_REFCOUNT, IS_WASM
)
from numpy.core._rational_tests import rational

Expand Down Expand Up @@ -556,6 +556,7 @@ def test_set(self):
np.seterr(**old)
assert_(np.geterr() == old)

@pytest.mark.skipif(IS_WASM, reason="no wasm fp exception support")
@pytest.mark.skipif(platform.machine() == "armv5tel", reason="See gh-413.")
def test_divide_err(self):
with np.errstate(divide='raise'):
Expand All @@ -565,6 +566,7 @@ def test_divide_err(self):
np.seterr(divide='ignore')
np.array([1.]) / np.array([0.])

@pytest.mark.skipif(IS_WASM, reason="no wasm fp exception support")
def test_errobj(self):
olderrobj = np.geterrobj()
self.called = 0
Expand Down Expand Up @@ -638,6 +640,7 @@ def assert_op_raises_fpe(self, fpeerr, flop, sc1, sc2):
self.assert_raises_fpe(fpeerr, flop, sc1[()], sc2[()])

# Test for all real and complex float types
@pytest.mark.skipif(IS_WASM, reason="no wasm fp exception support")
@pytest.mark.parametrize("typecode", np.typecodes["AllFloat"])
def test_floating_exceptions(self, typecode):
# Test basic arithmetic function errors
Expand Down Expand Up @@ -697,6 +700,7 @@ def test_floating_exceptions(self, typecode):
self.assert_raises_fpe(invalid,
lambda a, b: a*b, ftype(0), ftype(np.inf))

@pytest.mark.skipif(IS_WASM, reason="no wasm fp exception support")
def test_warnings(self):
# test warning code path
with warnings.catch_warnings(record=True) as w:
Expand Down Expand Up @@ -1584,6 +1588,7 @@ def __bool__(self):
a = np.array([[ThrowsAfter(15)]]*10)
assert_raises(ValueError, np.nonzero, a)

@pytest.mark.skipif(IS_WASM, reason="wasm doesn't have threads")
def test_structured_threadsafety(self):
# Nonzero (and some other functions) should be threadsafe for
# structured datatypes, see gh-15387. This test can behave randomly.
Expand Down
3 changes: 2 additions & 1 deletion numpy/core/tests/test_regression.py
Expand Up @@ -12,7 +12,7 @@
assert_, assert_equal, IS_PYPY, assert_almost_equal,
assert_array_equal, assert_array_almost_equal, assert_raises,
assert_raises_regex, assert_warns, suppress_warnings,
_assert_valid_refcount, HAS_REFCOUNT, IS_PYSTON
_assert_valid_refcount, HAS_REFCOUNT, IS_PYSTON, IS_WASM
)
from numpy.testing._private.utils import _no_tracing, requires_memory
from numpy.compat import asbytes, asunicode, pickle
Expand Down Expand Up @@ -326,6 +326,7 @@ def bfb():
assert_raises(ValueError, bfa)
assert_raises(ValueError, bfb)

@pytest.mark.xfail(IS_WASM, reason="not sure why")
@pytest.mark.parametrize("index",
[np.ones(10, dtype=bool), np.arange(10)],
ids=["boolean-arr-index", "integer-arr-index"])
Expand Down
5 changes: 4 additions & 1 deletion numpy/core/tests/test_ufunc.py
Expand Up @@ -13,7 +13,7 @@
from numpy.testing import (
assert_, assert_equal, assert_raises, assert_array_equal,
assert_almost_equal, assert_array_almost_equal, assert_no_warnings,
assert_allclose, HAS_REFCOUNT, suppress_warnings
assert_allclose, HAS_REFCOUNT, suppress_warnings, IS_WASM
)
from numpy.testing._private.utils import requires_memory
from numpy.compat import pickle
Expand Down Expand Up @@ -700,6 +700,7 @@ def test_sum_stability(self):
a = np.ones(500, dtype=np.float64)
assert_almost_equal((a / 10.).sum() - a.size / 10., 0, 13)

@pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
def test_sum(self):
for dt in (int, np.float16, np.float32, np.float64, np.longdouble):
for v in (0, 1, 2, 7, 8, 9, 15, 16, 19, 127,
Expand Down Expand Up @@ -2538,6 +2539,7 @@ def test_ufunc_input_casterrors(bad_offset):
np.add(arr, arr, dtype=np.intp, casting="unsafe")


@pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
@pytest.mark.parametrize("bad_offset", [0, int(np.BUFSIZE * 1.5)])
def test_ufunc_input_floatingpoint_error(bad_offset):
value = 123
Expand Down Expand Up @@ -2584,6 +2586,7 @@ def test_reduce_casterrors(offset):
assert out[()] < value * offset


@pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
@pytest.mark.parametrize("method",
[np.add.accumulate, np.add.reduce,
pytest.param(lambda x: np.add.reduceat(x, [0]), id="reduceat"),
Expand Down