Esempio n. 1
0
def test_with_header():
    header_name = "header-" + name().stem + ".h"
    cake = random.randint(0, 256)
    header = Header(DUMP / header_name, defines={"cake": cake})

    source, = anchor(name().with_suffix(".c"))
    source.write_text("""\
    #include "%s"

    int get_cake() {
        return cake;
    }
    """ % header_name)

    self = CSlug(*anchor(name()), source, headers=header)  # yapf: disable

    try:
        old = os.getcwd()
        os.chdir(DUMP)
        self.make()
    finally:
        os.chdir(old)

    assert self.headers[0].path.exists()
    assert self.dll.get_cake() == cake
Esempio n. 2
0
def test_basic(true_file):
    SOURCE = RESOURCES / "basic.c"
    if true_file:
        file = SOURCE
    else:
        file = io.StringIO(SOURCE.read_text())
    self = CSlug(*anchor(name(), file)) # yapf: disable

    assert not self.path.exists()
    assert not self.types_map.json_path.exists()
    self.dll
    assert self.path.exists()
    assert self.types_map.json_path.exists()

    assert hasattr(self.dll, "add_1")
    assert hasattr(self.dll, "times_2")

    assert self.dll.add_1.restype == ctypes.c_int
    assert self.dll.add_1.argtypes == [ctypes.c_int]
    assert self.dll.times_2.restype == ctypes.c_float
    assert self.dll.times_2.argtypes == [ctypes.c_float]

    assert isinstance(self.dll.add_1(3), int)
    assert self.dll.add_1(3) == 4
    assert isinstance(self.dll.times_2(6), float)
    assert self.dll.times_2(7.5) == 15
    assert self.dll.times_2(7) == 14.0

    with pytest.raises(Exception):
        self.dll.add_1()
    with pytest.raises(Exception):
        self.dll.add_1(2.0)
Esempio n. 3
0
def test_non_escaped_unicode_literal(source):
    """Test a C source file may contain unicode characters in string literals.
    """
    delayed_skip_if_unsupported("Unicode literals", pgcc=False)
    self = CSlug(anchor(name()), source)
    self.make()
    assert self.dll.a() == "㟐"
Esempio n. 4
0
def test_long_repr():
    slug = CSlug(
        anchor(name()),
        io.StringIO("""

    #include <stdint.h>

    typedef struct LongNamedThing{
        int64_t very_long_name_1; int64_t very_long_name_2;
        int64_t very_long_name_3; int64_t very_long_name_4;
    } LongNamedThing;

    """))

    thing = slug.dll.LongNamedThing(*(range(4)))

    assert repr(thing) == """
LongNamedThing(very_long_name_1=0, very_long_name_2=1,
               very_long_name_3=2, very_long_name_4=3)
""".lstrip()

    thing = slug.dll.LongNamedThing(*(1 << 30 << i for i in range(4)))

    assert repr(thing) == """
LongNamedThing(very_long_name_1=1073741824,
               very_long_name_2=2147483648,
               very_long_name_3=4294967296,
               very_long_name_4=8589934592)
""".lstrip()
Esempio n. 5
0
def test_bit_ness():
    """Check 32/64b-bits behaves as expected by looking at integer overflow.
    """
    from cslug._cslug import BIT_NESS

    self = CSlug(*anchor(name(), io.StringIO("""

        # include <stddef.h>
        # include <stdbool.h>

        bool adding_1_causes_overflow(size_t x) {
            return (x + 1) < x;
        }

    """))) # yapf: disable

    for int_size in range(8, 128, 8):
        # Maximum possible value for an unsigned int of size ``int_size``.
        int_max = (1 << int_size) - 1
        if BIT_NESS <= int_size:
            assert adding_1_causes_overflow_py(int_max)
            assert self.dll.adding_1_causes_overflow(int_max)

        else:
            assert not adding_1_causes_overflow_py(int_max)
            assert not self.dll.adding_1_causes_overflow(int_max)
Esempio n. 6
0
def test_custom_include():
    """``#include`` an awkwardly located header file using the ``-I`` option."""

    self = CSlug(anchor(name()),
                 io.StringIO("""\
        #include <header-in-random-location.h>

        int foo() { return REMOTE_CONSTANT; }

    """), flags=["-I", RESOURCES / "somewhere-remote"])  # yapf: disable

    assert self.dll.foo() == 13

    # Test without the custom include path. It should lead to a failed build.
    # This awkward construct asserts that an error is raised however pgcc
    # indeterminantly ignores such errors and produces non functional
    # exectaubles. In this case we should xfail().
    self.flags.clear()
    try:
        self.make()

        if cc_version()[0] == "pgcc":
            pytest.xfail("pgcc silently created an invalid executable.")
        else:
            assert 0, "No build error was raised"
    except exceptions.BuildError:
        return
Esempio n. 7
0
def test_bytes():
    self = CSlug(anchor(name()), DEMOS / "bytes" / "encrypt.c")
    self.make()

    def _crypt(data, key, multiplier):
        out = ctypes.create_string_buffer(len(data))
        self.dll.encrypt(data, len(data), out, key, len(key), multiplier)
        return out[:]

    def encrypt(data, key):
        return _crypt(data, key, 1)

    def decrypt(data, key):
        return _crypt(data, key, -1)

    data = bytes(range(256))
    key = b"secret"
    encrypted = encrypt(data, key)
    assert isinstance(encrypted, bytes)

    # A pure Python equivalent of the C code we're testing.
    pure_py = bytes(
        (i + j) % 256 for (i, j) in zip(data, itertools.cycle(key)))
    assert encrypted == pure_py

    assert decrypt(encrypted, key) == data
    leaks(lambda: encrypt(data, key), n=100, tol=len(data) * 10)
Esempio n. 8
0
def test_build_error():

    self = CSlug(*anchor(
        name(), io.StringIO("""
        int invalid() { syntax }
        """)))

    with pytest.raises(exceptions.BuildError):
        self.make()

    assert getattr(self, "_dll", None) is None
Esempio n. 9
0
def test_propagate_build_warnings():

    self = CSlug(*anchor(name(), io.StringIO("""
        #warning "Not a good idea."
        void foo() {  }
    """))) # yapf: disable

    with pytest.warns(exceptions.BuildWarning, match="Not a good idea."):
        self.make()

    assert hasattr(self.dll, "foo")
Esempio n. 10
0
def test_unicode_identifiers(source):
    """Test unicode function/variable names."""
    delayed_skip_if_unsupported("Unicode identifiers",
                                gcc=(10, ),
                                tcc=False,
                                pcc=False,
                                clang=(3, 3),
                                pgcc=False)

    slug = CSlug(anchor(name()), source)
    slug.make()
    assert slug.dll.㟐(5) == 4
Esempio n. 11
0
def test_str():
    self = CSlug(anchor(name()), DEMOS / "strings" / "reverse.c")
    self.make()

    def reverse_test(text):
        out = ctypes.create_unicode_buffer(len(text) + 1)
        self.dll.reverse(text, out, len(text)) is None
        assert out.value == text[::-1]
        assert out[:] == text[::-1] + "\x00"

    reverse_test("hello")

    # A 10th of the memory that would be leaked if `reverse_test()` leaked.
    tolerance = ctypes.sizeof(ctypes.create_unicode_buffer("hello" * 100)) * 10
    leaks(lambda: reverse_test("hello" * 100), n=100, tol=tolerance)
Esempio n. 12
0
def test_no_cc_or_blocked_error():

    self = CSlug(*anchor(name(), io.StringIO("")))

    old = os.environ.copy()
    try:
        misc.hide_from_PATH("gcc")
        misc.hide_from_PATH("clang")
        os.environ.pop("CC", None)
        with pytest.raises(exceptions.NoGccError):
            self.make()
    finally:
        os.environ.clear()
        os.environ.update(old)

    with pytest.raises(exceptions.BuildBlockedError):
        with misc.block_compile():
            self.make()
    str(exceptions.BuildBlockedError())

    assert self.make()
Esempio n. 13
0
def test_macos_arches(monkeypatch):
    """Test cross compiling for arm64/x86_64 on macOS."""

    if platform.system() != "Darwin":
        return
    xcode = _xcode_version()
    if xcode is None or xcode < (12, 2):
        pytest.skip("Needs Xcode >= 12.2")

    # The test C code should require linking to be meaningful.
    self = CSlug(anchor(name()), io.StringIO("""\
        #include <math.h>
        #include <stdlib.h>

        double take_sin(double x) { return sin(x); }
    """), links="m")  # yapf: disable

    for arches in ["x86_64", "arm64", "x86_64 arm64"]:
        monkeypatch.setenv("MACOS_ARCHITECTURE", arches)

        # Try compiling.
        try:
            self.make()
        except exceptions.BuildError:
            # This is unlikely to be cslug specific. More likely its Xcode
            # getting into a mess.
            pytest.skip("This compiler appears not to be setup to build cross "
                        "arch binaries.")

        # Verify which type(s) are included in the binary we just built using
        # lipo - a builtin command line tool for inspecting, slicing and making
        # fat binaries.
        _arches = run(["lipo", "-archs", str(self.path)],
                      stdout=PIPE).stdout.decode()
        assert sorted(arches.split()) == sorted(_arches.split())

        # If we just built a native binary or a dual binary then we can run it
        # and it should work.
        if platform.machine() in arches:
            self.dll.take_sin(2) == pytest.approx(math.sin(2))
Esempio n. 14
0
def test_contradictory_flags():
    """Assert that compilers accept contradictory flags without complaint."""

    # Should warn for implicit function declaration due to missing
    # #include <math.h>.
    self = CSlug(anchor(name()),
                 io.StringIO("double foo() { return sin(3); }"))

    try:
        with pytest.warns(exceptions.BuildWarning, match="implicit.*"):
            self.make()
    except exceptions.BuildError as ex:
        # The default clang on macOS elevates this to an error with -Werror.
        # Also fixable with flags="-Wno-error=implicit-function-declaration".
        assert "implicit" in str(ex)

    # Specify no warnings to override the default of all warnings -Wall.
    self.flags.append("-w")

    with warnings.catch_warnings():
        warnings.filterwarnings("error", category=exceptions.BuildWarning)
        self.make()
Esempio n. 15
0
def test_infinite_math():
    # Assert that the `-ffinite-math-only` optimizer flag (or something similar)
    # is not enabled.
    # This is why -Ofast should not be used.

    self = CSlug(anchor(name(), io.StringIO("""\
        #include <math.h>

        int is_nan(double x) { return isnan(x); }
        int is_finite(double x) { return isfinite(x); }

    """)))  # yapf: disable

    assert self.dll.is_nan(12) == 0
    assert self.dll.is_finite(12) == 1

    assert self.dll.is_nan(math.inf) == 0
    assert self.dll.is_finite(math.inf) == 0

    # Bizarrely, Windows gcc but not tcc returns -1 instead of 1.
    assert self.dll.is_nan(math.nan) in (-1, 1)
    assert self.dll.is_finite(math.nan) == 0
Esempio n. 16
0
def test_buffers_or_temporary_files():
    """Test CSlug.compile_command() only with both pseup and temporary files."""
    self = CSlug(*anchor(name(), io.StringIO("foo"), io.StringIO("bar")))

    # gcc does support pseudo files and therefore they should be used.
    command, buffers, temporary_files = \
        self.compile_command(_cc_version=("gcc", (10,)))
    assert "-" in command
    assert len(buffers) == 2
    assert len(temporary_files) == 0
    assert buffers[0].read() == "foo"
    for option in command:
        assert not option.endswith(".c")

    # pcc doesn't support pseudo files so temporary files must be used instead.
    command, buffers, temporary_files = \
        self.compile_command(_cc_version=("pcc", (1,)))
    assert "-" not in command
    assert len(buffers) == 0
    assert len(temporary_files) == 2
    assert temporary_files[0].name in command
    assert os.path.exists(temporary_files[0].name)
    assert temporary_files[0].name.endswith(".c")
Esempio n. 17
0
def test_names_not_in_dll():
    """
    Check that CSlug doesn't get into too much of a mess if it thinks a
    function should exist but doesn't.
    """

    # Both the functions in the C source below look to cslug like they would be
    # included in the DLL but in fact aren't.
    self = CSlug(*anchor(name(), io.StringIO("""

        inline int add_1(int x) { return x + 1; }

        #if 0
        float times_2(float y) { return y * 2.0; }
        #endif

    """))) # yapf: disable

    # Ensure built:
    self.dll

    # Check cslug found them.
    assert "add_1" in self.types_map.functions
    assert "times_2" in self.types_map.functions

    # But they are not in the DLL.

    # Inline functions may still be present. I believe this is gcc version
    # dependent.
    # assert not hasattr(self.dll, "add_1")
    # with pytest.raises(AttributeError):
    #     self.dll.add_1

    assert not hasattr(self.dll, "times_2")
    with pytest.raises(AttributeError):
        self.dll.time_2
Esempio n. 18
0
def test_struct_io():

    slug = CSlug(
        anchor(name()),
        io.StringIO("""

    typedef struct Thing{
        int a;
        float b;
    } Thing;

    int get_a(Thing * thing) {
        return thing -> a;
    }

    float get_b(Thing * thing) {
        return thing -> b;
    }

    void set_a(Thing * thing, int a) {
        thing -> a = a;
    }

    void set_b(Thing * thing, float b) {
        thing -> b = b;
    }

    Thing make_thing(int a, float b) {
        Thing thing;
        thing.a = a;
        thing.b = b;
        return thing;
    }

    int get_a_plus_x(Thing thing, int x) {
        return thing.a + x;
    }

    """))

    with warnings.catch_warnings():
        warnings.filterwarnings("error")
        lib = slug.dll

    assert hasattr(lib, "Thing")

    assert lib.get_a.argtypes == [ctypes.c_void_p]

    assert lib.make_thing.argtypes == [ctypes.c_int, ctypes.c_float]
    assert lib.make_thing.restype == lib.Thing
    assert lib.get_a_plus_x.argtypes == [lib.Thing, ctypes.c_int]
    assert lib.get_a_plus_x.restype == ctypes.c_int

    thing = lib.Thing(10, 3)
    assert lib.get_a(thing._ptr) == thing.a == 10
    assert lib.get_b(thing._ptr) == thing.b == 3.0
    assert repr(thing) == "Thing(a=10, b=3.0)"

    lib.set_a(thing._ptr, 5)
    lib.set_b(thing._ptr, 12)
    assert lib.get_a(thing._ptr) == thing.a == 5
    assert lib.get_b(thing._ptr) == thing.b == 12.0
Esempio n. 19
0
def test_remake():
    from cslug._stdlib import dlclose, null_free_dll, stdlib
    assert dlclose is not null_free_dll, \
        "A `dlclose()` function hasn't been found for this platform. It "\
        "should be added to `_cslug._stdlib.py`."

    slug = CSlug(anchor(name(), RESOURCES / "basic.c"))
    path = slug.dll._name

    # Having the DLL open should block writing to it on Windows.
    ref = ctypes.CDLL(path)

    try:
        slug.make()
    except exceptions.LibraryOpenElsewhereError as ex:
        # This will happen only on Windows.
        assert path in str(ex)

    if platform.system() == "FreeBSD":
        # dlclose() seems to complain no matter what I do with it.
        stdlib.dlerror.restype = ctypes.c_char_p
        # This fails with an unhelpful b"Service unavailable".
        # assert dlclose(ctypes.c_void_p(ref._handle)) == 0, stdlib.dlerror()
        # dlclosing is mainly for Windows, which causes Permission errors if
        # you forget it. As far as I know, this issue is harmless.
    else:
        assert dlclose(ctypes.c_void_p(ref._handle)) == 0

    # With the DLL closed make() should work.
    slug.make()

    # Each slug gets registered in `_slug_refs`. Check that this has happened.
    # `slug` should be the only one registered under this filename.
    from cslug._cslug import _slug_refs
    assert slug.path in _slug_refs
    assert len(_slug_refs[slug.path]) == 1
    assert _slug_refs[slug.path][0]() is slug

    # Create another slug with the same filename. It should join the 1st in the
    # register.
    other = CSlug(slug.name, *slug.sources)
    assert other.path == slug.path
    assert len(_slug_refs[slug.path]) == 2
    assert _slug_refs[slug.path][1]() is other

    # Get `other` to open its DLL.
    other_dll = other.dll
    assert other_dll.add_1(2) == 3
    # Make `slug` try to rebuild the DLL which is already opened by `other`.
    # This would normally cause mayhem but doesn't because `slug.make()`
    # implicitly calls`other.close()` so that it doesn't try to overwrite an
    # open file.
    slug.make()
    # `other.dll` should re-open automatically.
    assert other.dll is not other_dll

    # `other` is weakref-ed and should still be garbage-collectable despite
    # being in `_slug_refs`.
    del other_dll, other
    # Test the link in `_slug_refs` is dead.
    assert _slug_refs[slug.path][1]() is None

    # `_slug_refs` should be cleared of dead links on calling `close()`.
    slug.close()
    assert len(_slug_refs[slug.path]) == 1
Esempio n. 20
0
def test_empty():
    slug = CSlug(anchor(name()), io.StringIO("typedef struct Empty {} Empty;"))

    thing = slug.dll.Empty()

    assert repr(thing) == "Empty()"
Esempio n. 21
0
# -*- coding: utf-8 -*-
"""
"""

from cslug import CSlug, anchor, ptr, nc_ptr

slug = CSlug(anchor("flatten.c"))
slug.make()

import pytest

pytest.importorskip("numpy")
import numpy as np


def flatten_3D(arr):
    """Wrapper for ``flatten_3D()``."""

    # Normalise `arr`.
    arr = np.asarray(arr, dtype=np.double, order="C")
    # Sanity check that `arr` is 3D.
    assert arr.ndim == 3

    # Create a flat empty output array to populate.
    out = np.empty(arr.size, arr.dtype)

    # Pass `arr`, `out` and the shape of `arr` to the C function.
    # Note the use of `arr.ctypes.shape` which is a ctypes size_t array,
    # instead of `arr.shape` which is a tuple.
    slug.dll.flatten_3D(ptr(arr), ptr(out), ptr(arr.ctypes.shape))
Esempio n. 22
0
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# Disable converting single quotes to backtick and forward ticks but keep double
# dash and ellipsis markup.
smartquotes_action = "De"

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

rst_epilog = cslug.anchor("rst_epilog.txt")[0].read_text()

rst_epilog += """
.. role:: tooltip(raw)
   :format: html

"""

import sys

sys.path.insert(0, ".")
import tooltips_gen

rst_epilog += tooltips_gen.epilog

# Needed to use :c:`inline C code markup`.
Esempio n. 23
0
def test_escaped_unicode_literal(source):
    """Test a C source file may contain string literals containing escaped
    unicode characters."""
    self = CSlug(anchor(name()), source)
    self.make()
    assert self.dll.a() == "㟐"
Esempio n. 24
0
"""
"""

from typing import Union, Type, Tuple
import ctypes
import sys

import numpy as np
from cslug import CSlug, ptr, anchor, Header

from rockhopper import RequestMeError

NUMPY_REPR = False

BIG_ENDIAN = sys.byteorder == "big"
endians_header = Header(*anchor("src/endians.h", "src/endians.c"),
                        includes=["<stdbool.h>", '"_endian_typedefs.h"'])
slug = CSlug(anchor(
    "_slugs/ragged_array",
    "src/ragged_array.c",
    "src/ragged_array.h",
    "src/endians.c",
), headers=endians_header)  # yapf: disable

dtype_like = Union[np.dtype, Type[np.generic]]


def prod(iterable):
    """Equivalent to :func:`math.prod` introduced in Python 3.8. """
    out = 1
    for i in iterable:
Esempio n. 25
0
    class NameSpace:
        slug = CSlug(anchor(name(), io.StringIO("")))

        def make():
            raise CalledMake
Esempio n. 26
0
# -*- coding: utf-8 -*-
"""
"""

import numbers
import ctypes

from numbers import Number
from typing import Union, Tuple

import numpy as np
from cslug import CSlug, ptr, anchor, Header

hashes_header = Header(*anchor("hashes.h", "hashes.c"),
                       includes=["<stddef.h>", "<stdint.h>"])
slug = CSlug(anchor("hash_table", "hash_table.h", "hash_table.c", "hashes.c"),
             headers=hashes_header)

dtype_types = Union[np.dtype, np.generic, type, str, list, tuple]


class HashTable(object):
    """The raw core behind a set or a dictionary's keys.

    A hash table resembles a dictionary where its keys are the :attr:`keys`
    array but the values are an just an enumeration. It's core API:

    * Use :meth:`add` to add new keys if they've not been already added.
    * The :attr:`keys` lists all keys that have been added in the order that
      they were added.
    * Use :meth:`get` to retrieve indices of keys in :attr:`keys`.
Esempio n. 27
0
from cslug import CSlug, anchor

slug = CSlug(anchor("person.c"))

slug.make()
Esempio n. 28
0
from array import array
from cslug import CSlug, ptr, anchor

slug = CSlug(anchor("arrays-demo.c"))

slug.make()
assert slug.dll.sum(ptr(array("d", [10, 11, 12])), 3) == 33.0


def sum_(arr):
    """Wrapper for the ``sum()`` function from ``arrays-demos.c``."""
    # If not the correct type:
    if not (isinstance(arr, array) and arr.typecode == "d"):
        # Make it the correct type.
        arr = array("d", arr)
    # Run the C function.
    return slug.dll.sum(ptr(arr), len(arr))


assert sum_(range(10)) == 45

from cslug.misc import array_typecode

int32_t = array_typecode("int32_t")


def cumsum(x):
    # Some form of type check.
    if not (isinstance(x, array) and x.typecode == int32_t):
        x = array(int32_t, x)