From 7cb79aef2a12f29f1286caf3858001e47214f871 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 21 Nov 2019 20:54:41 +0000 Subject: [PATCH 10/19] tests: Test the Python plugin thoroughly. This tests the Python plugin thoroughly by issuing client commands through libnbd and checking we get the expected results. (cherry picked from commit 8ead4a82ec3227dbecb6cbfc419f1a18f2817d62) --- .gitignore | 1 + README | 2 + tests/Makefile.am | 15 +-- tests/test-lang-plugins.c | 3 +- tests/test-python-plugin.py | 133 +++++++++++++++++++++ tests/test-python.sh | 49 ++++++++ tests/test.py | 60 ---------- tests/test_python.py | 222 ++++++++++++++++++++++++++++++++++++ 8 files changed, 413 insertions(+), 72 deletions(-) create mode 100644 tests/test-python-plugin.py create mode 100755 tests/test-python.sh delete mode 100644 tests/test.py create mode 100755 tests/test_python.py diff --git a/.gitignore b/.gitignore index b25ac7fe..e25bd99b 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ Makefile.in /server/synopsis.c /server/test-public /stamp-h1 +/tests/__pycache__/ /tests/disk /tests/disk.gz /tests/disk.xz diff --git a/README b/README index 40f4cd37..05f1e060 100644 --- a/README +++ b/README @@ -130,6 +130,8 @@ For the Python plugin: - python development libraries + - python unittest to run the test suite + For the OCaml plugin: - OCaml >= 4.02.2 diff --git a/tests/Makefile.am b/tests/Makefile.am index d225cc63..09103fbb 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -67,6 +67,7 @@ EXTRA_PROGRAMS = TESTS_ENVIRONMENT = \ PATH=$(abs_top_builddir):$(PATH) \ SRCDIR=$(srcdir) \ + PYTHON=$(PYTHON) \ LIBGUESTFS_ATTACH_METHOD=appliance \ LIBGUESTFS_DEBUG=1 \ LIBGUESTFS_TRACE=1 \ @@ -160,7 +161,9 @@ EXTRA_DIST = \ test-probe-plugin.sh \ test-python-exception.sh \ test.pl \ - test.py \ + test_python.py \ + test-python-plugin.py \ + test-python.sh \ test-rate.sh \ test-rate-dynamic.sh \ test.rb \ @@ -801,18 +804,10 @@ endif HAVE_PERL if HAVE_PYTHON TESTS += \ + test-python.sh \ test-python-exception.sh \ test-shebang-python.sh \ $(NULL) -LIBGUESTFS_TESTS += test-python - -test_python_SOURCES = test-lang-plugins.c test.h -test_python_CFLAGS = \ - -DLANG='"python"' -DSCRIPT='"$(srcdir)/test.py"' \ - $(WARNINGS_CFLAGS) \ - $(LIBGUESTFS_CFLAGS) \ - $(NULL) -test_python_LDADD = libtest.la $(LIBGUESTFS_LIBS) endif HAVE_PYTHON diff --git a/tests/test-lang-plugins.c b/tests/test-lang-plugins.c index ffb19180..93f99381 100644 --- a/tests/test-lang-plugins.c +++ b/tests/test-lang-plugins.c @@ -56,8 +56,7 @@ main (int argc, char *argv[]) */ s = getenv ("NBDKIT_VALGRIND"); if (s && strcmp (s, "1") == 0 && - (strcmp (LANG, "python") == 0 || - strcmp (LANG, "ruby") == 0 || + (strcmp (LANG, "ruby") == 0 || strcmp (LANG, "tcl") == 0)) { fprintf (stderr, "%s test skipped under valgrind.\n", LANG); exit (77); /* Tells automake to skip the test. */ diff --git a/tests/test-python-plugin.py b/tests/test-python-plugin.py new file mode 100644 index 00000000..8e90bc23 --- /dev/null +++ b/tests/test-python-plugin.py @@ -0,0 +1,133 @@ +# nbdkit test plugin +# Copyright (C) 2019 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +"""See test-python.py.""" + +import nbdkit +import sys +import pickle +import base64 + +API_VERSION = 2 + +cfg = {} + +def config (k, v): + global cfg + if k == "cfg": + cfg = pickle.loads (base64.b64decode (v.encode())) + +def config_complete (): + print ("set_error = %r" % nbdkit.set_error) + +def open (readonly): + return { + 'disk': bytearray (cfg.get ('size', 0)) + } + +def get_size (h): + return len (h['disk']) + +def is_rotational (h): + return cfg.get ('is_rotational', False) + +def can_multi_conn (h): + return cfg.get ('can_multi_conn', False) + +def can_write (h): + return cfg.get ('can_write', True) + +def can_flush (h): + return cfg.get ('can_flush', False) + +def can_trim (h): + return cfg.get ('can_trim', False) + +def can_zero (h): + return cfg.get ('can_zero', False) + +def can_fast_zero (h): + return cfg.get ('can_fast_zero', False) + +def can_fua (h): + fua = cfg.get ('can_fua', "none") + if fua == "none": + return nbdkit.FUA_NONE + elif fua == "emulate": + return nbdkit.FUA_EMULATE + elif fua == "native": + return nbdkit.FUA_NATIVE + +def can_cache (h): + cache = cfg.get ('can_cache', "none") + if cache == "none": + return nbdkit.CACHE_NONE + elif cache == "emulate": + return nbdkit.CACHE_EMULATE + elif cache == "native": + return nbdkit.CACHE_NATIVE + +def pread (h, buf, offset, flags): + assert flags == 0 + end = offset + len(buf) + buf[:] = h['disk'][offset:end] + +def pwrite (h, buf, offset, flags): + expect_fua = cfg.get ('pwrite_expect_fua', False) + actual_fua = bool (flags & nbdkit.FLAG_FUA) + assert expect_fua == actual_fua + end = offset + len(buf) + h['disk'][offset:end] = buf + +def flush (h, flags): + assert flags == 0 + +def trim (h, count, offset, flags): + expect_fua = cfg.get ('trim_expect_fua', False) + actual_fua = bool (flags & nbdkit.FLAG_FUA) + assert expect_fua == actual_fua + h['disk'][offset:offset+count] = bytearray(count) + +def zero (h, count, offset, flags): + expect_fua = cfg.get ('zero_expect_fua', False) + actual_fua = bool (flags & nbdkit.FLAG_FUA) + assert expect_fua == actual_fua + expect_may_trim = cfg.get ('zero_expect_may_trim', False) + actual_may_trim = bool (flags & nbdkit.FLAG_MAY_TRIM) + assert expect_may_trim == actual_may_trim + expect_fast_zero = cfg.get ('zero_expect_fast_zero', False) + actual_fast_zero = bool (flags & nbdkit.FLAG_FAST_ZERO) + assert expect_fast_zero == actual_fast_zero + h['disk'][offset:offset+count] = bytearray(count) + +def cache (h, count, offset, flags): + assert flags == 0 + # do nothing diff --git a/tests/test-python.sh b/tests/test-python.sh new file mode 100755 index 00000000..50324d0f --- /dev/null +++ b/tests/test-python.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright (C) 2019 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +source ./functions.sh +set -e +set -x + +requires $PYTHON --version +requires $PYTHON -c 'import unittest' +requires $PYTHON -c 'import nbd' +requires test -f test_python.py +requires test -f test-python-plugin.py + +# Python has proven very difficult to valgrind, therefore it is disabled. +if [ "$NBDKIT_VALGRIND" = "1" ]; then + echo "$0: skipping Python test under valgrind." + exit 77 +fi + +$PYTHON -m unittest test_python diff --git a/tests/test.py b/tests/test.py deleted file mode 100644 index 4db56623..00000000 --- a/tests/test.py +++ /dev/null @@ -1,60 +0,0 @@ -import nbdkit - -disk = bytearray(1024*1024) - - -API_VERSION = 2 - - -def config_complete(): - print ("set_error = %r" % nbdkit.set_error) - - -def open(readonly): - return 1 - - -def get_size(h): - global disk - return len(disk) - - -def can_write(h): - return True - - -def can_flush(h): - return True - - -def is_rotational(h): - return False - - -def can_trim(h): - return True - - -def pread(h, buf, offset, flags): - global disk - end = offset + len(buf) - buf[:] = disk[offset:end] - - -def pwrite(h, buf, offset, flags): - global disk - end = offset + len(buf) - disk[offset:end] = buf - - -def flush(h, flags): - pass - - -def trim(h, count, offset, flags): - pass - - -def zero(h, count, offset, flags): - global disk - disk[offset:offset+count] = bytearray(count) diff --git a/tests/test_python.py b/tests/test_python.py new file mode 100755 index 00000000..6b9f2979 --- /dev/null +++ b/tests/test_python.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# nbdkit +# Copyright (C) 2019 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +""" +This tests the Python plugin thoroughly by issuing client commands +through libnbd and checking we get the expected results. It uses an +associated plugin (test-python-plugin.sh). +""" + +import os +import sys +import nbd +import unittest +import pickle +import base64 + +class Test (unittest.TestCase): + def setUp (self): + self.h = nbd.NBD () + + def tearDown (self): + del self.h + + def connect (self, cfg): + cfg = base64.b64encode (pickle.dumps (cfg)).decode() + cmd = ["nbdkit", "-v", "-s", "--exit-with-parent", + "python", "test-python-plugin.py", "cfg=" + cfg] + self.h.connect_command (cmd) + + def test_none (self): + """ + Test we can send an empty pickled test configuration and do + nothing else. This is just to ensure the machinery of the + test works. + """ + self.connect ({}) + + def test_size_512 (self): + """Test the size.""" + self.connect ({"size": 512}) + assert self.h.get_size() == 512 + + def test_size_1m (self): + """Test the size.""" + self.connect ({"size": 1024*1024}) + assert self.h.get_size() == 1024*1024 + + # Test each flag call. + def test_is_rotational_true (self): + self.connect ({"size": 512, "is_rotational": True}) + assert self.h.is_rotational() + + def test_is_rotational_false (self): + self.connect ({"size": 512, "is_rotational": False}) + assert not self.h.is_rotational() + + def test_can_multi_conn_true (self): + self.connect ({"size": 512, "can_multi_conn": True}) + assert self.h.can_multi_conn() + + def test_can_multi_conn_false (self): + self.connect ({"size": 512, "can_multi_conn": False}) + assert not self.h.can_multi_conn() + + def test_read_write (self): + self.connect ({"size": 512, "can_write": True}) + assert not self.h.is_read_only() + + def test_read_only (self): + self.connect ({"size": 512, "can_write": False}) + assert self.h.is_read_only() + + def test_can_flush_true (self): + self.connect ({"size": 512, "can_flush": True}) + assert self.h.can_flush() + + def test_can_flush_false (self): + self.connect ({"size": 512, "can_flush": False}) + assert not self.h.can_flush() + + def test_can_trim_true (self): + self.connect ({"size": 512, "can_trim": True}) + assert self.h.can_trim() + + def test_can_trim_false (self): + self.connect ({"size": 512, "can_trim": False}) + assert not self.h.can_trim() + + # nbdkit can always zero because it emulates it. + #self.connect ({"size": 512, "can_zero": True}) + #assert self.h.can_zero() + #self.connect ({"size": 512, "can_zero": False}) + #assert not self.h.can_zero() + + def test_can_fast_zero_true (self): + self.connect ({"size": 512, "can_fast_zero": True}) + assert self.h.can_fast_zero() + + def test_can_fast_zero_false (self): + self.connect ({"size": 512, "can_fast_zero": False}) + assert not self.h.can_fast_zero() + + def test_can_fua_none (self): + self.connect ({"size": 512, "can_fua": "none"}) + assert not self.h.can_fua() + + def test_can_fua_emulate (self): + self.connect ({"size": 512, "can_fua": "emulate"}) + assert self.h.can_fua() + + def test_can_fua_native (self): + self.connect ({"size": 512, "can_fua": "native"}) + assert self.h.can_fua() + + def test_can_cache_none (self): + self.connect ({"size": 512, "can_cache": "none"}) + assert not self.h.can_cache() + + def test_can_cache_emulate (self): + self.connect ({"size": 512, "can_cache": "emulate"}) + assert self.h.can_cache() + + def test_can_cache_native (self): + self.connect ({"size": 512, "can_cache": "native"}) + assert self.h.can_cache() + + # Not yet implemented: can_extents. + + def test_pread (self): + """Test pread.""" + self.connect ({"size": 512}) + buf = self.h.pread (512, 0) + assert buf == bytearray (512) + + # Test pwrite + flags. + def test_pwrite (self): + self.connect ({"size": 512}) + buf = bytearray (512) + self.h.pwrite (buf, 0) + + def test_pwrite_fua (self): + self.connect ({"size": 512, + "can_fua": "native", + "pwrite_expect_fua": True}) + buf = bytearray (512) + self.h.pwrite (buf, 0, nbd.CMD_FLAG_FUA) + + def test_flush (self): + """Test flush.""" + self.connect ({"size": 512, "can_flush": True}) + self.h.flush () + + # Test trim + flags. + def test_trim (self): + self.connect ({"size": 512, "can_trim": True}) + self.h.trim (512, 0) + + def test_trim_fua (self): + self.connect ({"size": 512, + "can_trim": True, + "can_fua": "native", + "trim_expect_fua": True}) + self.h.trim (512, 0, nbd.CMD_FLAG_FUA) + + # Test zero + flags. + def test_zero (self): + self.connect ({"size": 512, "can_zero": True}) + self.h.zero (512, 0, nbd.CMD_FLAG_NO_HOLE) + + def test_zero_fua (self): + self.connect ({"size": 512, + "can_zero": True, + "can_fua": "native", + "zero_expect_fua": True}) + self.h.zero (512, 0, nbd.CMD_FLAG_NO_HOLE | nbd.CMD_FLAG_FUA) + + def test_zero_may_trim (self): + self.connect ({"size": 512, + "can_zero": True, + "zero_expect_may_trim": True}) + self.h.zero (512, 0, 0) # absence of nbd.CMD_FLAG_NO_HOLE + + def test_zero_fast_zero (self): + self.connect ({"size": 512, + "can_zero": True, + "can_fast_zero": True, + "zero_expect_fast_zero": True}) + self.h.zero (512, 0, nbd.CMD_FLAG_NO_HOLE | nbd.CMD_FLAG_FAST_ZERO) + + def test_cache (self): + """Test cache.""" + self.connect ({"size": 512, "can_cache": "native"}) + self.h.cache (512, 0) -- 2.18.2