diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c49077bb27a2af158624e7222d999973487f0951..449e5e2227c63121d1aeb6c824423c4fbb4e54aa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -65,16 +65,21 @@ minimal-windows:
     - python -c "import numpy"
     - python setup.py quicktest
 
-minimal-ubuntu:
+ubuntu:
   stage: test
   except:
     variables:
       - $ENABLE_NIGHTLY_BUILDS
-  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/minimal_ubuntu
+  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/ubuntu
   script:
-    - python3 setup.py quicktest
+    - mkdir -p ~/.config/matplotlib
+    - echo "backend:template" > ~/.config/matplotlib/matplotlibrc
+    - sed -i 's/--doctest-modules //g' pytest.ini
+    - pytest-3 -v -m "not longrun"
   tags:
     - docker
+    - cuda
+    - AVX
 
 minimal-conda:
   stage: test
diff --git a/conftest.py b/conftest.py
index 3bbdefe7c1f3e03ad87d07449d7d7214e18bfeba..272f42303e620cd13fc7f26648bc03d437b1fda6 100644
--- a/conftest.py
+++ b/conftest.py
@@ -44,7 +44,7 @@ collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/autodiff.py")]
 try:
     import pycuda
 except ImportError:
-    collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/pystencils_tests/test_cudagpu.py")]
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils_tests/test_cudagpu.py")]
     add_path_to_ignore('pystencils/gpucuda')
 
 try:
@@ -73,7 +73,22 @@ try:
     import blitzdb
 except ImportError:
     add_path_to_ignore('pystencils/runhelper')
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils_tests/test_parameterstudy.py")]
 
+try:
+    import islpy
+except ImportError:
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/integer_set_analysis.py")]
+
+try:
+    import graphviz
+except ImportError:
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/backends/dot.py")]
+
+try:
+    import pyevtk
+except ImportError:
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/datahandling/vtk.py")]
 
 collect_ignore += [os.path.join(SCRIPT_FOLDER, 'setup.py')]
 
@@ -129,7 +144,7 @@ class IPyNbFile(pytest.File):
         exporter.exclude_markdown = True
         exporter.exclude_input_prompt = True
 
-        notebook_contents = self.fspath.open()
+        notebook_contents = self.fspath.open(encoding='utf-8')
 
         with warnings.catch_warnings():
             warnings.filterwarnings("ignore", "IPython.core.inputsplitter is deprecated")
diff --git a/doc/notebooks/01_tutorial_getting_started.ipynb b/doc/notebooks/01_tutorial_getting_started.ipynb
index 97ff73da1cf3bb63e19fa6fac122499120d71cee..70b3c2a8a3e86d81fb8972ad50ffbdd0ad4bcb89 100644
--- a/doc/notebooks/01_tutorial_getting_started.ipynb
+++ b/doc/notebooks/01_tutorial_getting_started.ipynb
@@ -932,14 +932,18 @@
     }
    ],
    "source": [
-    "import requests\n",
-    "import imageio\n",
-    "from io import BytesIO\n",
+    "try:\n",
+    "    import requests\n",
+    "    import imageio\n",
+    "    from io import BytesIO\n",
     "\n",
-    "response = requests.get(\"https://www.python.org/static/img/python-logo.png\")\n",
-    "img = imageio.imread(BytesIO(response.content)).astype(np.double)\n",
-    "img /= img.max()\n",
-    "plt.imshow(img);"
+    "    response = requests.get(\"https://www.python.org/static/img/python-logo.png\")\n",
+    "    img = imageio.imread(BytesIO(response.content)).astype(np.double)\n",
+    "    img /= img.max()\n",
+    "    plt.imshow(img);\n",
+    "except ImportError:\n",
+    "    print(\"No requests installed\")\n",
+    "    img = np.random.random((82, 290, 4))"
    ]
   },
   {
diff --git a/doc/notebooks/demo_plotting_and_animation.ipynb b/doc/notebooks/demo_plotting_and_animation.ipynb
index 07c980788f7617b1bfb8bfad8a13f2f2d7b5c33d..13d658a9cb4980a78cc984caeeb78e220b543553 100644
--- a/doc/notebooks/demo_plotting_and_animation.ipynb
+++ b/doc/notebooks/demo_plotting_and_animation.ipynb
@@ -6,7 +6,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from pystencils.session import *"
+    "from pystencils.session import *\n",
+    "\n",
+    "import shutil"
    ]
   },
   {
@@ -408,9 +410,12 @@
     }
    ],
    "source": [
-    "plt.figure()\n",
-    "animation = plt.scalar_field_animation(run_func, frames=60)\n",
-    "ps.jupyter.display_as_html_video(animation)"
+    "if shutil.which(\"ffmpeg\") is not None:\n",
+    "    plt.figure()\n",
+    "    animation = plt.scalar_field_animation(run_func, frames=60)\n",
+    "    ps.jupyter.display_as_html_video(animation)\n",
+    "else:\n",
+    "    print(\"No ffmpeg installed\")"
    ]
   },
   {
@@ -474,8 +479,11 @@
     }
    ],
    "source": [
-    "animation = plt.surface_plot_animation(run_func, frames=60)\n",
-    "ps.jupyter.display_as_html_video(animation)"
+    "if shutil.which(\"ffmpeg\") is not None:\n",
+    "    animation = plt.surface_plot_animation(run_func, frames=60)\n",
+    "    ps.jupyter.display_as_html_video(animation)\n",
+    "else:\n",
+    "    print(\"No ffmpeg installed\")"
    ]
   },
   {
@@ -636,9 +644,12 @@
     }
    ],
    "source": [
-    "plt.figure()\n",
-    "animation = plt.vector_field_animation(run_func, frames=60)\n",
-    "ps.jupyter.display_as_html_video(animation)"
+    "if shutil.which(\"ffmpeg\") is not None:\n",
+    "    plt.figure()\n",
+    "    animation = plt.vector_field_animation(run_func, frames=60)\n",
+    "    ps.jupyter.display_as_html_video(animation)\n",
+    "else:\n",
+    "    print(\"No ffmpeg installed\")"
    ]
   },
   {
@@ -671,8 +682,11 @@
     }
    ],
    "source": [
-    "animation = plt.vector_field_magnitude_animation(run_func, frames=60)\n",
-    "ps.jupyter.display_as_html_video(animation)"
+    "if shutil.which(\"ffmpeg\") is not None:\n",
+    "    animation = plt.vector_field_magnitude_animation(run_func, frames=60)\n",
+    "    ps.jupyter.display_as_html_video(animation)\n",
+    "else:\n",
+    "    print(\"No ffmpeg installed\")"
    ]
   },
   {
diff --git a/doc/notebooks/demo_wave_equation.ipynb b/doc/notebooks/demo_wave_equation.ipynb
index a4d5a9ff7c1cf5df33b83d3d48c3272f214a4d39..2aa083d96b33e6b2c69bee0783e1c55b4ab616fd 100644
--- a/doc/notebooks/demo_wave_equation.ipynb
+++ b/doc/notebooks/demo_wave_equation.ipynb
@@ -8,7 +8,9 @@
    },
    "outputs": [],
    "source": [
-    "from pystencils.session import *"
+    "from pystencils.session import *\n",
+    "\n",
+    "import shutil"
    ]
   },
   {
@@ -397,8 +399,11 @@
     }
    ],
    "source": [
-    "ani = plt.surface_plot_animation(run, zlim=(-1, 1))\n",
-    "ps.jupyter.display_as_html_video(ani)"
+    "if shutil.which(\"ffmpeg\") is not None:\n",
+    "    ani = plt.surface_plot_animation(run, zlim=(-1, 1))\n",
+    "    ps.jupyter.display_as_html_video(ani)\n",
+    "else:\n",
+    "    print(\"No ffmpeg installed\")"
    ]
   },
   {
@@ -466,9 +471,12 @@
     "            u_arrays[0], u_arrays[1], u_arrays[2] = u_arrays[1], u_arrays[2], u_arrays[0]\n",
     "        return u_arrays[2]\n",
     "    \n",
-    "    ani = plt.surface_plot_animation(run_LLVM, zlim=(-1, 1))\n",
     "    assert np.isfinite(np.max(u_arrays[2]))\n",
-    "ps.jupyter.display_as_html_video(ani)"
+    "    if shutil.which(\"ffmpeg\") is not None:\n",
+    "        ani = plt.surface_plot_animation(run_LLVM, zlim=(-1, 1))\n",
+    "        ps.jupyter.display_as_html_video(ani)\n",
+    "    else:\n",
+    "        print(\"No ffmpeg installed\")"
    ]
   },
   {
diff --git a/pystencils/astnodes.py b/pystencils/astnodes.py
index 062d3687f582423bd38b8a1c2e9dae3ff95a6bfb..8ec80917af20aefc5883c68ac51f360005b4350b 100644
--- a/pystencils/astnodes.py
+++ b/pystencils/astnodes.py
@@ -291,7 +291,10 @@ class Block(Node):
         self._nodes = nodes
         self.parent = None
         for n in self._nodes:
-            n.parent = self
+            try:
+                n.parent = self
+            except AttributeError:
+                pass
 
     @property
     def args(self):
diff --git a/pystencils/backends/cbackend.py b/pystencils/backends/cbackend.py
index 3f016810a9a673035adf1130039bc95e5d6ab70d..ad339a4bdc2a2a5d696c964d602fbc6d01fcfaa9 100644
--- a/pystencils/backends/cbackend.py
+++ b/pystencils/backends/cbackend.py
@@ -387,6 +387,13 @@ class CustomSympyPrinter(CCodePrinter):
             return self._print(expr.args[0])
         elif isinstance(expr, fast_inv_sqrt):
             return "({})".format(self._print(1 / sp.sqrt(expr.args[0])))
+        elif isinstance(expr, sp.Abs):
+            return "abs({})".format(self._print(expr.args[0]))
+        elif isinstance(expr, sp.Mod):
+            if expr.args[0].is_integer and expr.args[1].is_integer:
+                return "({} % {})".format(self._print(expr.args[0]), self._print(expr.args[1]))
+            else:
+                return "fmod({}, {})".format(self._print(expr.args[0]), self._print(expr.args[1]))
         elif expr.func in infix_functions:
             return "(%s %s %s)" % (self._print(expr.args[0]), infix_functions[expr.func], self._print(expr.args[1]))
         elif expr.func == int_power_of_2:
diff --git a/pystencils/cpu/cpujit.py b/pystencils/cpu/cpujit.py
index 6376ffb85bff4dca347e0dde7044a1d92ad7bab7..07a8a84d9c6d10ed8580a7ef83d6720b522cfd98 100644
--- a/pystencils/cpu/cpujit.py
+++ b/pystencils/cpu/cpujit.py
@@ -270,7 +270,8 @@ if( PyErr_Occurred() ) {{ return NULL; }}
 template_extract_complex = """
 PyObject * obj_{name} = PyDict_GetItemString(kwargs, "{name}");
 if( obj_{name} == NULL) {{  PyErr_SetString(PyExc_TypeError, "Keyword argument '{name}' missing"); return NULL; }};
-{target_type} {name}{{ {extract_function_real}( obj_{name} ), {extract_function_imag}( obj_{name} ) }};
+{target_type} {name}{{ ({real_type}) {extract_function_real}( obj_{name} ),
+                       ({real_type}) {extract_function_imag}( obj_{name} ) }};
 if( PyErr_Occurred() ) {{ return NULL; }}
 """
 
@@ -409,6 +410,8 @@ def create_function_boilerplate_code(parameter_info, name, insert_checks=True):
                 pre_call_code += template_extract_complex.format(extract_function_real=extract_function[0],
                                                                  extract_function_imag=extract_function[1],
                                                                  target_type=target_type,
+                                                                 real_type="float" if target_type == "ComplexFloat"
+                                                                           else "double",
                                                                  name=param.symbol.name)
             else:
                 pre_call_code += template_extract_scalar.format(extract_function=extract_function,
diff --git a/pystencils/fd/derivative.py b/pystencils/fd/derivative.py
index 02d8f74ee912ecbc59b80db0e757e27a26d4686e..31daf4294f4ffa70c766632c4126dd7403690251 100644
--- a/pystencils/fd/derivative.py
+++ b/pystencils/fd/derivative.py
@@ -316,7 +316,8 @@ def expand_diff_full(expr, functions=None, constants=None):
             functions.difference_update(constants)
 
     def visit(e):
-        e = e.expand()
+        if not isinstance(e, sp.Tuple):
+            e = e.expand()
 
         if e.func == Diff:
             result = 0
@@ -341,6 +342,9 @@ def expand_diff_full(expr, functions=None, constants=None):
             return result
         elif isinstance(e, sp.Piecewise):
             return sp.Piecewise(*((expand_diff_full(a, functions, constants), b) for a, b in e.args))
+        elif isinstance(expr, sp.Tuple):
+            new_args = [visit(arg) for arg in e.args]
+            return sp.Tuple(*new_args)
         else:
             new_args = [visit(arg) for arg in e.args]
             return e.func(*new_args) if new_args else e
@@ -380,6 +384,9 @@ def expand_diff_linear(expr, functions=None, constants=None):
                 return diff.split_linear(functions)
     elif isinstance(expr, sp.Piecewise):
         return sp.Piecewise(*((expand_diff_linear(a, functions, constants), b) for a, b in expr.args))
+    elif isinstance(expr, sp.Tuple):
+        new_args = [expand_diff_linear(e, functions) for e in expr.args]
+        return sp.Tuple(*new_args)
     else:
         new_args = [expand_diff_linear(e, functions) for e in expr.args]
         result = sp.expand(expr.func(*new_args) if new_args else expr)
diff --git a/pystencils/include/cuda_complex.hpp b/pystencils/include/cuda_complex.hpp
index ad555264a87881d8eaee6b2476c482039d606f71..535aa52e30781a03e170298e8e5ebbed872585dc 100644
--- a/pystencils/include/cuda_complex.hpp
+++ b/pystencils/include/cuda_complex.hpp
@@ -1173,53 +1173,53 @@ operator<<(std::basic_ostream<_CharT, _Traits> &__os, const complex<_Tp> &__x) {
 template <class U, class V>
 CUDA_CALLABLE_MEMBER auto operator*(const complex<U> &complexNumber,
                                     const V &scalar) -> complex<U> {
-  return complex<U>{real(complexNumber) * scalar, imag(complexNumber) * scalar};
+  return complex<U>(real(complexNumber) * scalar, imag(complexNumber) * scalar);
 }
 
 template <class U, class V>
 CUDA_CALLABLE_MEMBER auto operator*(const V &scalar,
                                     const complex<U> &complexNumber)
     -> complex<U> {
-  return complex<U>{real(complexNumber) * scalar, imag(complexNumber) * scalar};
+  return complex<U>(real(complexNumber) * scalar, imag(complexNumber) * scalar);
 }
 
 template <class U, class V>
 CUDA_CALLABLE_MEMBER auto operator+(const complex<U> &complexNumber,
                                     const V &scalar) -> complex<U> {
-  return complex<U>{real(complexNumber) + scalar, imag(complexNumber)};
+  return complex<U>(real(complexNumber) + scalar, imag(complexNumber));
 }
 
 template <class U, class V>
 CUDA_CALLABLE_MEMBER auto operator+(const V &scalar,
                                     const complex<U> &complexNumber)
     -> complex<U> {
-  return complex<U>{real(complexNumber) + scalar, imag(complexNumber)};
+  return complex<U>(real(complexNumber) + scalar, imag(complexNumber));
 }
 
 template <class U, class V>
 CUDA_CALLABLE_MEMBER auto operator-(const complex<U> &complexNumber,
                                     const V &scalar) -> complex<U> {
-  return complex<U>{real(complexNumber) - scalar, imag(complexNumber)};
+  return complex<U>(real(complexNumber) - scalar, imag(complexNumber));
 }
 
 template <class U, class V>
 CUDA_CALLABLE_MEMBER auto operator-(const V &scalar,
                                     const complex<U> &complexNumber)
     -> complex<U> {
-  return complex<U>{scalar - real(complexNumber), imag(complexNumber)};
+  return complex<U>(scalar - real(complexNumber), imag(complexNumber));
 }
 
 template <class U, class V>
 CUDA_CALLABLE_MEMBER auto operator/(const complex<U> &complexNumber,
                                     const V scalar) -> complex<U> {
-  return complex<U>{real(complexNumber) / scalar, imag(complexNumber) / scalar};
+  return complex<U>(real(complexNumber) / scalar, imag(complexNumber) / scalar);
 }
 
 template <class U, class V>
 CUDA_CALLABLE_MEMBER auto operator/(const V scalar,
                                     const complex<U> &complexNumber)
     -> complex<U> {
-  return complex<U>{scalar, 0} / complexNumber;
+  return complex<U>(scalar, 0) / complexNumber;
 }
 
 using ComplexDouble = complex<double>;
diff --git a/pystencils/kernelcreation.py b/pystencils/kernelcreation.py
index aaca6d3124c4303ae289e831fdb7b7680369c408..2c8aabaa530f985f7b645a41a29a9537a655fa07 100644
--- a/pystencils/kernelcreation.py
+++ b/pystencils/kernelcreation.py
@@ -79,14 +79,14 @@ def create_kernel(assignments,
                [0., 0., 0., 0., 0.]])
     """
     # ----  Normalizing parameters
+    if isinstance(assignments, Assignment):
+        assignments = [assignments]
     assert assignments, "Assignments must not be empty!"
     split_groups = ()
     if isinstance(assignments, AssignmentCollection):
         if 'split_groups' in assignments.simplification_hints:
             split_groups = assignments.simplification_hints['split_groups']
         assignments = assignments.all_assignments
-    if isinstance(assignments, Assignment):
-        assignments = [assignments]
 
     # ----  Creating ast
     if target == 'cpu':
diff --git a/pystencils_tests/test_boundary.py b/pystencils_tests/test_boundary.py
index 096b1348fc59181e14eb15042d8db0098e71c521..23770c8ef6d61c15110f94875b0000c3e7d11fac 100644
--- a/pystencils_tests/test_boundary.py
+++ b/pystencils_tests/test_boundary.py
@@ -3,6 +3,8 @@ from tempfile import TemporaryDirectory
 
 import numpy as np
 
+import pytest
+
 from pystencils import Assignment, create_kernel
 from pystencils.boundaries import BoundaryHandling, Neumann, add_neumann_boundary
 from pystencils.datahandling import SerialDataHandling
@@ -83,5 +85,6 @@ def test_kernel_vs_copy_boundary():
     np.testing.assert_almost_equal(python_copy_result, handling_result)
 
     with TemporaryDirectory() as tmp_dir:
+        pytest.importorskip('pyevtk')
         boundary_handling.geometry_to_vtk(file_name=os.path.join(tmp_dir, 'test_output1'), ghost_layers=False)
         boundary_handling.geometry_to_vtk(file_name=os.path.join(tmp_dir, 'test_output2'), ghost_layers=True)
diff --git a/pystencils_tests/test_buffer_gpu.py b/pystencils_tests/test_buffer_gpu.py
index fc27a2331be1690b8bb1d0da043b47b3ba6fcfc9..f9ee96e9b19c147d95f006de4221980900375cb1 100644
--- a/pystencils_tests/test_buffer_gpu.py
+++ b/pystencils_tests/test_buffer_gpu.py
@@ -22,6 +22,7 @@ FIELD_SIZES = [(4, 3), (9, 3, 7)]
 
 
 def _generate_fields(dt=np.uint8, stencil_directions=1, layout='numpy'):
+    pytest.importorskip('pycuda')
     field_sizes = FIELD_SIZES
     if stencil_directions > 1:
         field_sizes = [s + (stencil_directions,) for s in field_sizes]
@@ -44,7 +45,6 @@ def _generate_fields(dt=np.uint8, stencil_directions=1, layout='numpy'):
     return fields
 
 
-@pytest.mark.gpu
 def test_full_scalar_field():
     """Tests fully (un)packing a scalar field (from)to a GPU buffer."""
     fields = _generate_fields()
@@ -73,7 +73,6 @@ def test_full_scalar_field():
         np.testing.assert_equal(src_arr, dst_arr)
 
 
-@pytest.mark.gpu
 def test_field_slice():
     """Tests (un)packing slices of a scalar field (from)to a buffer."""
     fields = _generate_fields()
@@ -109,7 +108,6 @@ def test_field_slice():
             np.testing.assert_equal(src_arr[pack_slice], dst_arr[unpack_slice])
 
 
-@pytest.mark.gpu
 def test_all_cell_values():
     """Tests (un)packing all cell values of the a field (from)to a buffer."""
     num_cell_values = 7
@@ -148,7 +146,6 @@ def test_all_cell_values():
         np.testing.assert_equal(src_arr, dst_arr)
 
 
-@pytest.mark.gpu
 def test_subset_cell_values():
     """Tests (un)packing a subset of cell values of the a field (from)to a buffer."""
     num_cell_values = 7
@@ -190,7 +187,6 @@ def test_subset_cell_values():
         np.testing.assert_equal(dst_arr, mask_arr.filled(int(0)))
 
 
-@pytest.mark.gpu
 def test_field_layouts():
     num_cell_values = 7
     for layout_str in ['numpy', 'fzyx', 'zyxf', 'reverse_numpy']:
diff --git a/pystencils_tests/test_complex_numbers.py b/pystencils_tests/test_complex_numbers.py
index 2fb558cbc883ca89f81667ea3d03ffd46eafc3bf..41dc76a40411129c4ee68f0ee42028b57875d71e 100644
--- a/pystencils_tests/test_complex_numbers.py
+++ b/pystencils_tests/test_complex_numbers.py
@@ -57,6 +57,9 @@ def test_complex_numbers(assignment, scalar_dtypes, target):
     print(code)
     assert "Not supported" not in code
 
+    if target == 'gpu':
+        pytest.importorskip('pycuda')
+
     kernel = ast.compile()
     assert kernel is not None
 
@@ -100,6 +103,9 @@ def test_complex_numbers_64(assignment, target):
     print(code)
     assert "Not supported" not in code
 
+    if target == 'gpu':
+        pytest.importorskip('pycuda')
+
     kernel = ast.compile()
     assert kernel is not None
 
@@ -125,6 +131,7 @@ def test_complex_execution(dtype, target, with_complex_argument):
     })
 
     if target == 'gpu':
+        pytest.importorskip('pycuda')
         from pycuda.gpuarray import zeros
         x_arr = zeros((20, 30), complex_dtype)
         y_arr = zeros((20, 30), complex_dtype)
diff --git a/pystencils_tests/test_cuda_known_functions.py b/pystencils_tests/test_cuda_known_functions.py
index c610d9e8517f25352a1853448f35889da16f1c23..dcdabeaef6934e06b289351127b168d77d803be9 100644
--- a/pystencils_tests/test_cuda_known_functions.py
+++ b/pystencils_tests/test_cuda_known_functions.py
@@ -1,5 +1,7 @@
 import sympy
 
+import pytest
+
 import pystencils
 from pystencils.astnodes import get_dummy_symbol
 from pystencils.backends.cuda_backend import CudaSympyPrinter
@@ -18,6 +20,7 @@ def test_cuda_known_functions():
     })
 
     ast = pystencils.create_kernel(assignments, 'gpu')
+    pytest.importorskip('pycuda')
     pystencils.show_code(ast)
     kernel = ast.compile()
     assert(kernel is not None)
diff --git a/pystencils_tests/test_custom_backends.py b/pystencils_tests/test_custom_backends.py
index 6b0fc6fd1307426bd2b2a88549570f8e2fca2a07..6500076ce278bff4fc2a81f65c55553bd9523d78 100644
--- a/pystencils_tests/test_custom_backends.py
+++ b/pystencils_tests/test_custom_backends.py
@@ -25,7 +25,7 @@ class ScreamingGpuBackend(CudaBackend):
         return normal_code.upper()
 
 
-def test_custom_backends():
+def test_custom_backends_cpu():
     z, x, y = pystencils.fields("z, y, x: [2d]")
 
     normal_assignments = pystencils.AssignmentCollection([pystencils.Assignment(
@@ -36,6 +36,16 @@ def test_custom_backends():
     with pytest.raises(CalledProcessError):
         pystencils.cpu.cpujit.make_python_function(ast, custom_backend=ScreamingBackend())
 
+
+def test_custom_backends_gpu():
+    pytest.importorskip('pycuda')
+    import pycuda.driver
+
+    z, x, y = pystencils.fields("z, y, x: [2d]")
+
+    normal_assignments = pystencils.AssignmentCollection([pystencils.Assignment(
+        z[0, 0], x[0, 0] * sympy.log(x[0, 0] * y[0, 0]))], [])
+
     ast = pystencils.create_kernel(normal_assignments, target='gpu')
     pystencils.show_code(ast, ScreamingGpuBackend())
     with pytest.raises(pycuda.driver.CompileError):
diff --git a/pystencils_tests/test_datahandling.py b/pystencils_tests/test_datahandling.py
index 1b79fdbec5aa8bbbfb90450d2014d058065e998b..7f95fe1f04d1bbf72770d2837bf6723681c1f50d 100644
--- a/pystencils_tests/test_datahandling.py
+++ b/pystencils_tests/test_datahandling.py
@@ -128,6 +128,7 @@ def kernel_execution_jacobi(dh, target):
 
 
 def vtk_output(dh):
+    pytest.importorskip('pyevtk')
     dh.add_array('scalar_field')
     dh.add_array('vector_field', values_per_cell=dh.dim)
     dh.add_array('multiple_scalar_field', values_per_cell=9)
@@ -223,6 +224,7 @@ def test_kernel_param(target):
 
 
 def test_vtk_output():
+    pytest.importorskip('pyevtk')
     for domain_shape in [(4, 5), (3, 4, 5)]:
         dh = create_data_handling(domain_size=domain_shape, periodicity=True)
         vtk_output(dh)
diff --git a/pystencils_tests/test_field.py b/pystencils_tests/test_field.py
index a2813e34f9c72c8bbcd848d21993c181cf31fc43..253ca9f26547d78e6b23b2c6dbc582196b49960d 100644
--- a/pystencils_tests/test_field.py
+++ b/pystencils_tests/test_field.py
@@ -44,7 +44,7 @@ def test_error_handling():
         Field.create_generic('f', spatial_dimensions=2, index_dimensions=1, dtype=struct_dtype)
     assert 'index dimension' in str(e.value)
 
-    arr = np.array([[1, 2.0, 3], [1, 2.0, 3]], dtype=struct_dtype)
+    arr = np.array([[[(1,)*3, (2,)*3, (3,)*3]]*2], dtype=struct_dtype)
     Field.create_from_numpy_array('f', arr, index_dimensions=0)
     with pytest.raises(ValueError) as e:
         Field.create_from_numpy_array('f', arr, index_dimensions=1)
diff --git a/pystencils_tests/test_interpolation.py b/pystencils_tests/test_interpolation.py
index b35427f51619ecad198a7a512c2744204c3c43dd..477765bb31289dbfdc48927e3cc55f10d49f16a0 100644
--- a/pystencils_tests/test_interpolation.py
+++ b/pystencils_tests/test_interpolation.py
@@ -14,8 +14,6 @@ import numpy as np
 import pytest
 import sympy
 
-import pycuda.autoinit  # NOQA
-import pycuda.gpuarray as gpuarray
 import pystencils
 from pystencils.interpolation_astnodes import LinearInterpolator
 from pystencils.spatial_coordinates import x_, y_
@@ -51,7 +49,7 @@ def test_interpolation():
     print(assignments)
     ast = pystencils.create_kernel(assignments)
     print(ast)
-    pystencils.show_code(ast)
+    print(pystencils.show_code(ast))
     kernel = ast.compile()
 
     pyconrad.imshow(lenna)
@@ -71,7 +69,7 @@ def test_scale_interpolation():
         print(assignments)
         ast = pystencils.create_kernel(assignments)
         print(ast)
-        pystencils.show_code(ast)
+        print(pystencils.show_code(ast))
         kernel = ast.compile()
 
         out = np.zeros_like(lenna)
@@ -83,9 +81,9 @@ def test_scale_interpolation():
                          ['border',
                           'clamp',
                           pytest.param('warp', marks=pytest.mark.xfail(
-                              reason="Fails on newer SymPy version due to complex conjugate()")),
+                              reason="requires interpolation-refactoring branch")),
                           pytest.param('mirror', marks=pytest.mark.xfail(
-                              reason="Fails on newer SymPy version due to complex conjugate()")),
+                              reason="requires interpolation-refactoring branch")),
                           ])
 def test_rotate_interpolation(address_mode):
     """
@@ -102,7 +100,7 @@ def test_rotate_interpolation(address_mode):
     print(assignments)
     ast = pystencils.create_kernel(assignments)
     print(ast)
-    pystencils.show_code(ast)
+    print(pystencils.show_code(ast))
     kernel = ast.compile()
 
     out = np.zeros_like(lenna)
@@ -114,7 +112,10 @@ def test_rotate_interpolation(address_mode):
 @pytest.mark.parametrize('address_mode', ('border', 'wrap', 'clamp', 'mirror'))
 @pytest.mark.parametrize('use_textures', ('use_textures', False))
 def test_rotate_interpolation_gpu(dtype, address_mode, use_textures):
+    pytest.importorskip('pycuda')
 
+    import pycuda.gpuarray as gpuarray
+    import pycuda.autoinit  # noqa
     rotation_angle = sympy.pi / 5
     scale = 1
 
@@ -141,49 +142,56 @@ def test_rotate_interpolation_gpu(dtype, address_mode, use_textures):
     kernel(x=lenna_gpu, y=out)
     pyconrad.imshow(out,
                     f"out {address_mode} texture:{use_textures} {type_map[dtype]}")
-    skimage.io.imsave(f"/tmp/out {address_mode} texture:{use_textures} {type_map[dtype]}.tif",
-                      np.ascontiguousarray(out.get(), np.float32))
 
 
-@pytest.mark.parametrize('address_mode', ['border', 'wrap', 'clamp', 'mirror'])
+@pytest.mark.parametrize('address_mode', ['border', 'wrap',
+                                          pytest.param('warp', marks=pytest.mark.xfail(
+                                              reason="% printed as fmod on old sympy")),
+                                          pytest.param('mirror', marks=pytest.mark.xfail(
+                                              reason="% printed as fmod on old sympy")),
+                                          ])
 @pytest.mark.parametrize('dtype', [np.float64, np.float32, np.int32])
 @pytest.mark.parametrize('use_textures', ('use_textures', False,))
 def test_shift_interpolation_gpu(address_mode, dtype, use_textures):
+    sver = sympy.__version__.split(".")
+    if (int(sver[0]) == 1 and int(sver[1]) < 2) and address_mode in ['mirror', 'warp']:
+        pytest.skip()
+    pytest.importorskip('pycuda')
+
+    import pycuda.gpuarray as gpuarray
+    import pycuda.autoinit  # noqa
 
     rotation_angle = 0  # sympy.pi / 5
     scale = 1
     # shift = - sympy.Matrix([1.5, 1.5])
     shift = sympy.Matrix((0.0, 0.0))
 
-    for dtype in [np.float64, np.float32, np.int32]:
-        if dtype == np.int32:
-            lenna_gpu = gpuarray.to_gpu(
-                np.ascontiguousarray(lenna * 255, dtype))
-        else:
-            lenna_gpu = gpuarray.to_gpu(
-                np.ascontiguousarray(lenna, dtype))
-        for use_textures in [True, False]:
-            x_f, y_f = pystencils.fields('x,y: %s [2d]' % type_map[dtype], ghost_layers=0)
-
-            if use_textures:
-                transformed = scale * sympy.rot_axis3(rotation_angle)[:2, :2] * sympy.Matrix((x_, y_)) + shift
-            else:
-                transformed = scale * sympy.rot_axis3(rotation_angle)[:2, :2] * sympy.Matrix((x_, y_)) + shift
-            assignments = pystencils.AssignmentCollection({
-                y_f.center(): LinearInterpolator(x_f, address_mode=address_mode).at(transformed)
-            })
-            # print(assignments)
-            ast = pystencils.create_kernel(assignments, target='gpu', use_textures_for_interpolation=use_textures)
-            # print(ast)
-            pystencils.show_code(ast)
-            kernel = ast.compile()
-
-            out = gpuarray.zeros_like(lenna_gpu)
-            kernel(x=lenna_gpu, y=out)
-            pyconrad.imshow(out,
-                            f"out {address_mode} texture:{use_textures} {type_map[dtype]}")
-            skimage.io.imsave(f"/tmp/out {address_mode} texture:{use_textures} {type_map[dtype]}.tif",
-                              np.ascontiguousarray(out.get(), np.float32))
+    if dtype == np.int32:
+        lenna_gpu = gpuarray.to_gpu(
+            np.ascontiguousarray(lenna * 255, dtype))
+    else:
+        lenna_gpu = gpuarray.to_gpu(
+            np.ascontiguousarray(lenna, dtype))
+
+    x_f, y_f = pystencils.fields('x,y: %s [2d]' % type_map[dtype], ghost_layers=0)
+
+    if use_textures:
+        transformed = scale * sympy.rot_axis3(rotation_angle)[:2, :2] * sympy.Matrix((x_, y_)) + shift
+    else:
+        transformed = scale * sympy.rot_axis3(rotation_angle)[:2, :2] * sympy.Matrix((x_, y_)) + shift
+    assignments = pystencils.AssignmentCollection({
+        y_f.center(): LinearInterpolator(x_f, address_mode=address_mode).at(transformed)
+    })
+    # print(assignments)
+    ast = pystencils.create_kernel(assignments, target='gpu', use_textures_for_interpolation=use_textures)
+    # print(ast)
+    print(pystencils.show_code(ast))
+    kernel = ast.compile()
+
+    out = gpuarray.zeros_like(lenna_gpu)
+    kernel(x=lenna_gpu, y=out)
+    pyconrad.imshow(out,
+                    f"out {address_mode} texture:{use_textures} {type_map[dtype]}")
 
 
 @pytest.mark.parametrize('address_mode', ['border', 'clamp'])
@@ -202,7 +210,7 @@ def test_rotate_interpolation_size_change(address_mode):
     print(assignments)
     ast = pystencils.create_kernel(assignments)
     print(ast)
-    pystencils.show_code(ast)
+    print(pystencils.show_code(ast))
     kernel = ast.compile()
 
     out = np.zeros((100, 150), np.float64)
@@ -221,7 +229,7 @@ def test_field_interpolated(address_mode, target):
     print(assignments)
     ast = pystencils.create_kernel(assignments, target=target)
     print(ast)
-    pystencils.show_code(ast)
+    print(pystencils.show_code(ast))
     kernel = ast.compile()
 
     out = np.zeros_like(lenna)
diff --git a/pystencils_tests/test_loop_cutting.py b/pystencils_tests/test_loop_cutting.py
index cd89f37f6f365b4223e1463db68874f50e81c46d..0291074b87c051bcf381883e474078c663274ac1 100644
--- a/pystencils_tests/test_loop_cutting.py
+++ b/pystencils_tests/test_loop_cutting.py
@@ -1,6 +1,8 @@
 import numpy as np
 import sympy as sp
 
+import pytest
+
 import pystencils as ps
 import pystencils.astnodes as ast
 from pystencils.field import Field, FieldType
@@ -57,6 +59,7 @@ def test_staggered_iteration():
                                sum(f[o] for o in offsets_in_plane(d, -1, dim)))
         assignments = [ps.Assignment(s.staggered_access(d), expressions[i]) for i, d in enumerate(s.staggered_stencil)]
         func_optimized = create_staggered_kernel(assignments).compile()
+        pytest.importorskip('islpy')
         assert not func_optimized.ast.atoms(Conditional), "Loop cutting optimization did not work"
 
         func(f=f_arr, s=s_arr_ref)
@@ -99,6 +102,7 @@ def test_staggered_iteration_manual():
     move_constants_before_loop(kernel_ast.body)
     cleanup_blocks(kernel_ast.body)
 
+    pytest.importorskip('islpy')
     assert not kernel_ast.atoms(Conditional), "Loop cutting optimization did not work"
 
     func_optimized = make_python_function(kernel_ast)
diff --git a/pystencils_tests/test_opencl.py b/pystencils_tests/test_opencl.py
index ac9aaad9f8584fead6fa4ea198cd6c7419226139..6c013cefed7387827888385feeb11352b96cc8e6 100644
--- a/pystencils_tests/test_opencl.py
+++ b/pystencils_tests/test_opencl.py
@@ -246,11 +246,12 @@ def test_kernel_creation():
 
     print(assignments)
 
+    import pystencils.opencl.autoinit
     ast = pystencils.create_kernel(assignments, target='opencl')
 
     print(ast.backend)
 
-    code = str(pystencils.show_code(ast))
+    code = pystencils.get_code_str(ast)
     print(code)
     assert 'get_local_size' in code
 
diff --git a/pystencils_tests/test_phasefield_dentritic_3D.ipynb b/pystencils_tests/test_phasefield_dentritic_3D.ipynb
index aebdeefdddf53e9736feb7d8673c09bc5d8fa6d0..1a01f103891fb229385d8192f263fb53dd837a78 100644
--- a/pystencils_tests/test_phasefield_dentritic_3D.ipynb
+++ b/pystencils_tests/test_phasefield_dentritic_3D.ipynb
@@ -1,5 +1,15 @@
 {
  "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pytest\n",
+    "pytest.importorskip('pycuda')"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 1,
diff --git a/pystencils_tests/test_plot.py b/pystencils_tests/test_plot.py
index 6234334bb91352c80c643c474c76c15b968ce1c7..4c9207216266e48d4f2b0b14db1a5e160732c5cb 100644
--- a/pystencils_tests/test_plot.py
+++ b/pystencils_tests/test_plot.py
@@ -1,5 +1,8 @@
 import os
 from tempfile import TemporaryDirectory
+import shutil
+
+import pytest
 
 import numpy as np
 
@@ -20,6 +23,7 @@ def example_vector_field(t=0, shape=(40, 40)):
     return result
 
 
+@pytest.mark.skipif(shutil.which('ffmpeg') is None, reason="ffmpeg not available")
 def test_animation():
     t = 0
 
diff --git a/pystencils_tests/test_print_infinity.py b/pystencils_tests/test_print_infinity.py
index 4b1e488822163e684dcad4009fca1b72c7d87d2c..9e2dbd29b18389265b206513ec6c4625e0722a10 100644
--- a/pystencils_tests/test_print_infinity.py
+++ b/pystencils_tests/test_print_infinity.py
@@ -17,6 +17,9 @@ def test_print_infinity(type, negative, target):
         assignment = pystencils.Assignment(x.center, oo)
     ast = pystencils.create_kernel(assignment, data_type=type, target=target)
 
+    if target == 'gpu':
+        pytest.importorskip('pycuda')
+
     ast.compile()
 
     print(ast.compile().code)
diff --git a/pystencils_tests/test_random.py b/pystencils_tests/test_random.py
index 473aa3d1215449f51e70aa3b09f2c605ed13c313..1b3f89f2f81aa6e079f7bc030f27da74e778dd13 100644
--- a/pystencils_tests/test_random.py
+++ b/pystencils_tests/test_random.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+import pytest
+
 import pystencils as ps
 from pystencils.rng import PhiloxFourFloats, PhiloxTwoDoubles, AESNIFourFloats, AESNITwoDoubles
 
@@ -12,6 +14,9 @@ philox_reference = np.array([[[3576608082, 1252663339, 1987745383,  348040302],
 
 def test_philox_double():
     for target in ('cpu', 'gpu'):
+        if target == 'gpu':
+            pytest.importorskip('pycuda')
+
         dh = ps.create_data_handling((2, 2), default_ghost_layers=0, default_target=target)
         f = dh.add_array("f", values_per_cell=2)
 
@@ -39,6 +44,9 @@ def test_philox_double():
 
 def test_philox_float():
     for target in ('cpu', 'gpu'):
+        if target == 'gpu':
+            pytest.importorskip('pycuda')
+
         dh = ps.create_data_handling((2, 2), default_ghost_layers=0, default_target=target)
         f = dh.add_array("f", values_per_cell=4)
 
diff --git a/pystencils_tests/test_staggered_kernel.py b/pystencils_tests/test_staggered_kernel.py
index 310220d21442dc1d1a24b0e74e50f3e068374b67..c4f491393d2b4d08fbb9b4c99356abdc7e7199b6 100644
--- a/pystencils_tests/test_staggered_kernel.py
+++ b/pystencils_tests/test_staggered_kernel.py
@@ -1,6 +1,8 @@
 import numpy as np
 import sympy as sp
 
+import pytest
+
 import pystencils as ps
 
 
@@ -88,6 +90,7 @@ def test_staggered_subexpressions():
 
 
 def test_staggered_loop_cutting():
+    pytest.importorskip('islpy')
     dh = ps.create_data_handling((4, 4), periodicity=True, default_target='cpu')
     j = dh.add_array('j', values_per_cell=4, field_type=ps.FieldType.STAGGERED)
     assignments = [ps.Assignment(j.staggered_access("SW"), 1)]
diff --git a/pytest.ini b/pytest.ini
index 4e9f5caddfef20639da974ad9327e53fa8b6ced4..0070425966626449378fcd1ff9eabe468256a112 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -3,7 +3,6 @@ python_files = test_*.py *_test.py scenario_*.py
 norecursedirs = *.egg-info .git .cache .ipynb_checkpoints htmlcov
 addopts = --doctest-modules --durations=20  --cov-config pytest.ini
 markers =
-       gpu: test that require a gpu
        kerncraft: tests depending on kerncraft
 
 [run]