diff --git a/run_tests.py b/run_tests.py index a84f3bcea9862f7b4b9998d3b7e1be881d456a94..04a525c441fab9de22e45ce6176458bebbc1f1c4 100755 --- a/run_tests.py +++ b/run_tests.py @@ -10,11 +10,10 @@ from src import abstract from src import abstract_th if __name__ == '__main__': - print("Don't forget to turn at least one scanner on !") - #print("---") - #print("=== RawAPI: ===") - #unittest.TextTestRunner(verbosity=3).run( - # tests_rawapi.get_all_tests()) + print("---") + print("=== RawAPI: ===") + unittest.TextTestRunner(verbosity=3).run( + tests_rawapi.get_all_tests()) print("---") print("=== Abstract: ===") unittest.TextTestRunner(verbosity=3).run( diff --git a/src/abstract.py b/src/abstract.py index df12ecf051be09bef5ca32ee00f4f0684dff53c5..d3514c7247db92a57609acf84d9d73d570d95098 100644 --- a/src/abstract.py +++ b/src/abstract.py @@ -13,6 +13,7 @@ from .rawapi import SaneException from .rawapi import SaneStatus from .rawapi import SaneUnit from .rawapi import SaneValueType +from .rawapi import sane_init, sane_exit __all__ = [ @@ -32,36 +33,12 @@ __all__ = [ # We use huge buffers to spend the maximum amount of time in non-Python code SANE_READ_BUFSIZE = 512*1024 -sane_is_init = 0 -sane_version = None - # XXX(Jflesch): Never open more than one handle at the same time. # Some Sane backends don't support it. For instance, I have 2 HP scanners, and # if I try to access both from the same process, I get I/O errors. sane_dev_handle = ("", None) -def sane_init(): - global sane_is_init - global sane_version - if sane_is_init <= 0: - sane_version = rawapi.sane_init() - sane_is_init += 1 - return sane_version - - -def sane_exit(): - # TODO(Jflesch): This is a workaround - # In a multithreaded environment, for some unknown reason, - # calling sane_exit() will work but the program will crash - # when stopping. So we simply never call sane_exit() ... - pass - #global sane_is_init - #sane_is_init -= 1 - #if sane_is_init <= 0: - # rawapi.sane_exit() - - class ScannerOption(object): idx = 0 name = "" diff --git a/src/rawapi.py b/src/rawapi.py index 104d69559bad11dd636bb96cf746269a7b2083b7..f96ecda4a6c944f93d50723d7b98d344d67ace25 100644 --- a/src/rawapi.py +++ b/src/rawapi.py @@ -36,6 +36,8 @@ __all__ = [ 'sane_strstatus', ] +sane_is_init = 0 +sane_version = None try: SANE_LIB = ctypes.cdll.LoadLibrary("libsane.so.1") @@ -497,9 +499,13 @@ def is_sane_available(): def sane_init(auth_callback=__dummy_auth_callback): - global sane_available + global sane_available, sane_is_init, sane_version assert(sane_available) + if sane_is_init > 0: + return sane_version + + sane_is_init += 1 version_code = ctypes.c_int() wrap_func = __AuthCallbackWrapper(auth_callback).wrapper auth_callback = AUTH_CALLBACK_DEF(wrap_func) @@ -511,14 +517,22 @@ def sane_init(auth_callback=__dummy_auth_callback): major = (version_code >> 24) & 0xFF minor = (version_code >> 16) & 0xFF build = (version_code >> 0) & 0xFFFF - return SaneVersion(major, minor, build) + sane_version = SaneVersion(major, minor, build) + return sane_version def sane_exit(): - global sane_available + global sane_available, sane_is_init assert(sane_available) - SANE_LIB.sane_exit() + # TODO(Jflesch): This is a workaround + # In a multithreaded environment, for some unknown reason, + # calling sane_exit() will work but the program will crash + # when stopping. So we simply never call sane_exit() ... + + # sane_is_init -= 1 + # if sane_is_init <= 0: + # SANE_LIB.sane_exit() def sane_get_devices(local_only=False): diff --git a/tests/tests_abstract.py b/tests/tests_abstract.py index 51fa6ff6e0f1662313b733a7bb41b8a8ef3751fe..f2e2b1a165efbb0e9af2e03b20a281499f37fa34 100644 --- a/tests/tests_abstract.py +++ b/tests/tests_abstract.py @@ -15,6 +15,11 @@ class TestSaneGetDevices(unittest.TestCase): def test_get_devices(self): devices = self.module.get_devices() + if len(devices) == 0: + # if there are no devices found, create a virtual device. + # see sane-test(5) and /etc/sane.d/test.conf + self.module.Scanner("test")._open() + devices = self.module.get_devices() self.assertTrue(len(devices) > 0) def tearDown(self): @@ -31,8 +36,11 @@ class TestSaneOptions(unittest.TestCase): def test_get_option(self): for dev in self.devices: - val = dev.options['mode'].value - self.assertNotEqual(val, None) + for (k, v) in dev.options.items(): + if v.capabilities.is_active(): + self.assertNotEqual(v.value, None) + else: + self.assertRaises(self.module.SaneException, lambda: v.value) def test_set_option(self): for dev in self.devices: @@ -40,22 +48,31 @@ class TestSaneOptions(unittest.TestCase): val = dev.options['mode'].value self.assertEqual(val, "Gray") - def __set_opt(self, opt_name, opt_val): - for dev in self.devices: - dev.options[opt_name].value = opt_val + def __set_opt(self, dev, opt_name, opt_val): + dev.options[opt_name].value = opt_val def test_set_inexisting_option(self): - self.assertRaises(KeyError, self.__set_opt, 'xyz', "Gray") + for dev in self.devices: + self.assertRaises(KeyError, self.__set_opt, dev, 'xyz', "Gray") def test_set_invalid_value(self): - self.assertRaises(self.module.SaneException, self.__set_opt, 'mode', "XYZ") + for dev in self.devices: + self.assertRaises(self.module.SaneException, self.__set_opt, dev, 'mode', "XYZ") + + def test_set_inactive_option(self): + for dev in self.devices: + noncolor = [x for x in dev.options["mode"].constraint if x != "Color"] + if len(noncolor) == 0: + self.skipTest("scanner does not support required option") + dev.options["mode"].value = noncolor[0] + # three-pass mode is only active in color mode + self.assertRaises(self.module.SaneException, self.__set_opt, dev, 'three-pass', 1) def tearDown(self): for dev in self.devices: del(dev) del(self.devices) - class TestSaneScan(unittest.TestCase): def set_module(self, module): self.module = module @@ -66,8 +83,10 @@ class TestSaneScan(unittest.TestCase): self.dev = devices[0] def test_simple_scan_lineart(self): - self.assertTrue("Lineart" in self.dev.options['mode'].constraint) - self.dev.options['mode'].value = "Lineart" + try: + self.dev.options['mode'].value = "Lineart" + except self.module.SaneException: + self.skipTest("scanner does not support required option") scan_session = self.dev.scan(multiple=False) try: assert(scan_session.scan is not None) @@ -79,8 +98,10 @@ class TestSaneScan(unittest.TestCase): self.assertNotEqual(img, None) def test_simple_scan_gray(self): - self.assertTrue("Gray" in self.dev.options['mode'].constraint) - self.dev.options['mode'].value = "Gray" + try: + self.dev.options['mode'].value = "Gray" + except self.module.SaneException: + self.skipTest("scanner does not support required option") scan_session = self.dev.scan(multiple=False) try: while True: @@ -91,8 +112,10 @@ class TestSaneScan(unittest.TestCase): self.assertNotEqual(img, None) def test_simple_scan_color(self): - self.assertTrue("Color" in self.dev.options['mode'].constraint) - self.dev.options['mode'].value = "Color" + try: + self.dev.options['mode'].value = "Color" + except self.module.SaneException: + self.skipTest("scanner does not support required option") scan_session = self.dev.scan(multiple=False) try: while True: @@ -103,9 +126,11 @@ class TestSaneScan(unittest.TestCase): self.assertNotEqual(img, None) def test_multi_scan_on_flatbed(self): - self.assertTrue("Flatbed" in self.dev.options['source'].constraint) - self.dev.options['source'].value = "Flatbed" - self.dev.options['mode'].value = "Color" + try: + self.dev.options['source'].value = "Flatbed" + self.dev.options['mode'].value = "Color" + except self.module.SaneException: + self.skipTest("scanner does not support required option") scan_session = self.dev.scan(multiple=True) try: while True: @@ -116,9 +141,17 @@ class TestSaneScan(unittest.TestCase): self.assertNotEqual(scan_session.images[0], None) def test_multi_scan_on_adf(self): - self.assertTrue("ADF" in self.dev.options['source'].constraint) - self.dev.options['source'].value = "ADF" - self.dev.options['mode'].value = "Color" + # sane-test uses 'Automatic Document Feeder' instead of ADF + try: + if "ADF" in self.dev.options['source'].constraint: + self.dev.options['source'].value = "ADF" + pages = 0 + elif "Automatic Document Feeder" in self.dev.options['source'].constraint: + self.dev.options['source'].value = "Automatic Document Feeder" + pages = 10 # sane-test scans give us 10 pages + self.dev.options['mode'].value = "Color" + except self.module.SaneException: + self.skipTest("scanner does not support required option") scan_session = self.dev.scan(multiple=True) try: while True: @@ -128,12 +161,14 @@ class TestSaneScan(unittest.TestCase): pass except StopIteration: pass - self.assertEqual(len(scan_session.images), 0) + self.assertEqual(len(scan_session.images), pages) def test_expected_size(self): - self.assertTrue("ADF" in self.dev.options['source'].constraint) - self.dev.options['source'].value = "Flatbed" - self.dev.options['mode'].value = "Color" + try: + self.dev.options['source'].value = "Flatbed" + self.dev.options['mode'].value = "Color" + except self.module.SaneException: + self.skipTest("scanner does not support required option") scan_session = self.dev.scan(multiple=False) scan_size = scan_session.scan.expected_size self.assertTrue(scan_size[0] > 100) @@ -141,9 +176,11 @@ class TestSaneScan(unittest.TestCase): scan_session.scan.cancel() def test_get_progressive_scan(self): - self.assertTrue("ADF" in self.dev.options['source'].constraint) - self.dev.options['source'].value = "Flatbed" - self.dev.options['mode'].value = "Color" + try: + self.dev.options['source'].value = "Flatbed" + self.dev.options['mode'].value = "Color" + except self.module.SaneException: + self.skipTest("scanner does not support required option") scan_session = self.dev.scan(multiple=False) last_line = 0 expected_size = scan_session.scan.expected_size @@ -190,6 +227,7 @@ def get_all_tests(module): TestSaneOptions("test_set_option"), TestSaneOptions("test_set_inexisting_option"), TestSaneOptions("test_set_invalid_value"), + TestSaneOptions("test_set_inactive_option"), ] for test in tests: test.set_module(module) diff --git a/tests/tests_rawapi.py b/tests/tests_rawapi.py index 207fc92f09453cd9c70730d451957fc7aadda65c..37235489f1640691c990b92aa69695fb0f4794bc 100644 --- a/tests/tests_rawapi.py +++ b/tests/tests_rawapi.py @@ -6,6 +6,17 @@ import unittest import rawapi +def get_test_devices(): + '''Return SANE devices, perhaps after creating a test device.''' + devices = rawapi.sane_get_devices() + if len(devices) == 0: + # if there are no devices found, create a virtual device. + # see sane-test(5) and /etc/sane.d/test.conf + rawapi.sane_close(rawapi.sane_open("test")) + devices = rawapi.sane_get_devices() + return devices + + class TestSaneInit(unittest.TestCase): def setUp(self): pass @@ -23,7 +34,7 @@ class TestSaneGetDevices(unittest.TestCase): rawapi.sane_init() def test_get_devices(self): - devices = rawapi.sane_get_devices() + devices = get_test_devices() self.assertTrue(len(devices) > 0) def tearDown(self): @@ -33,7 +44,7 @@ class TestSaneGetDevices(unittest.TestCase): class TestSaneOpen(unittest.TestCase): def setUp(self): rawapi.sane_init() - devices = rawapi.sane_get_devices() + devices = get_test_devices() self.assertTrue(len(devices) > 0) self.dev_name = devices[0].name @@ -51,7 +62,7 @@ class TestSaneOpen(unittest.TestCase): class TestSaneGetOptionDescriptor(unittest.TestCase): def setUp(self): rawapi.sane_init() - devices = rawapi.sane_get_devices() + devices = get_test_devices() self.assertTrue(len(devices) > 0) dev_name = devices[0].name self.dev_handle = rawapi.sane_open(dev_name) @@ -85,7 +96,7 @@ class TestSaneGetOptionDescriptor(unittest.TestCase): class TestSaneControlOption(unittest.TestCase): def setUp(self): rawapi.sane_init() - devices = rawapi.sane_get_devices() + devices = get_test_devices() self.assertTrue(len(devices) > 0) dev_name = devices[0].name self.dev_handle = rawapi.sane_open(dev_name) @@ -96,6 +107,8 @@ class TestSaneControlOption(unittest.TestCase): desc = rawapi.sane_get_option_descriptor(self.dev_handle, opt_idx) if not rawapi.SaneValueType(desc.type).can_getset_opt(): continue + if desc.cap|rawapi.SaneCapabilities.INACTIVE == desc.cap: + continue val = rawapi.sane_get_option_value(self.dev_handle, opt_idx) self.assertNotEqual(val, None) @@ -122,7 +135,7 @@ class TestSaneControlOption(unittest.TestCase): class TestSaneScan(unittest.TestCase): def setUp(self): rawapi.sane_init() - devices = rawapi.sane_get_devices() + devices = get_test_devices() self.assertTrue(len(devices) > 0) dev_name = devices[0].name self.dev_handle = rawapi.sane_open(dev_name) @@ -132,7 +145,10 @@ class TestSaneScan(unittest.TestCase): # with my scanner #rawapi.sane_set_io_mode(self.dev_handle, non_blocking=False) - rawapi.sane_start(self.dev_handle) + try: + rawapi.sane_start(self.dev_handle) + except StopIteration: + self.skipTest("cannot scan, no document loaded") # XXX(Jflesch): get_select_fd() always return SANE_STATUS_UNSUPPORTED # with my scanner @@ -148,7 +164,10 @@ class TestSaneScan(unittest.TestCase): rawapi.sane_cancel(self.dev_handle) def test_cancelled_scan(self): - rawapi.sane_start(self.dev_handle) + try: + rawapi.sane_start(self.dev_handle) + except StopIteration: + self.skipTest("cannot scan, no document loaded") buf = rawapi.sane_read(self.dev_handle, 128*1024) self.assertTrue(len(buf) > 0) rawapi.sane_cancel(self.dev_handle)