diff --git a/backend/Makefile.am b/backend/Makefile.am index d1c8a7977..17e44377a 100644 --- a/backend/Makefile.am +++ b/backend/Makefile.am @@ -520,6 +520,7 @@ libgenesys_la_SOURCES = genesys/genesys.cpp genesys/genesys.h \ genesys/tables_motor_profile.cpp \ genesys/tables_sensor.cpp \ genesys/test_scanner_interface.h genesys/test_scanner_interface.cpp \ + genesys/test_settings.h genesys/test_settings.cpp \ genesys/test_usb_device.h genesys/test_usb_device.cpp \ genesys/usb_device.h genesys/usb_device.cpp \ genesys/low.cpp genesys/low.h \ diff --git a/backend/genesys/genesys.cpp b/backend/genesys/genesys.cpp index 97dd3f902..20ed6e9c3 100644 --- a/backend/genesys/genesys.cpp +++ b/backend/genesys/genesys.cpp @@ -65,6 +65,8 @@ #include "usb_device.h" #include "utilities.h" #include "scanner_interface_usb.h" +#include "test_scanner_interface.h" +#include "test_settings.h" #include "../include/sane/sanei_config.h" #include "../include/sane/sanei_magic.h" @@ -4424,9 +4426,15 @@ config_attach_genesys(SANEI_Config __sane_unused__ *config, const char *devname) } /* probes for scanner to attach to the backend */ -static void probe_genesys_devices (void) +static void probe_genesys_devices() { DBG_HELPER(dbg); + if (is_testing_mode()) { + attach_usb_device(get_testing_device_name().c_str(), + get_testing_vendor_id(), get_testing_product_id()); + return; + } + SANEI_Config config; // set configuration options structure : no option for this backend @@ -4630,19 +4638,23 @@ void sane_init_impl(SANE_Int * version_code, SANE_Auth_Callback authorize) DBG_INIT (); DBG_HELPER_ARGS(dbg, "authorize %s null", authorize ? "!=" : "=="); DBG(DBG_init, "SANE Genesys backend from %s\n", PACKAGE_STRING); + + if (!is_testing_mode()) { #ifdef HAVE_LIBUSB - DBG(DBG_init, "SANE Genesys backend built with libusb-1.0\n"); + DBG(DBG_init, "SANE Genesys backend built with libusb-1.0\n"); #endif #ifdef HAVE_LIBUSB_LEGACY - DBG(DBG_init, "SANE Genesys backend built with libusb\n"); + DBG(DBG_init, "SANE Genesys backend built with libusb\n"); #endif + } if (version_code) { *version_code = SANE_VERSION_CODE(SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, 0); } - /* init usb use */ - sanei_usb_init (); + if (!is_testing_mode()) { + sanei_usb_init(); + } /* init sanei_magic */ sanei_magic_init(); @@ -4686,7 +4698,9 @@ sane_exit_impl(void) { DBG_HELPER(dbg); - sanei_usb_exit(); + if (!is_testing_mode()) { + sanei_usb_exit(); + } run_functions_at_backend_exit(); } @@ -4701,9 +4715,11 @@ void sane_get_devices_impl(const SANE_Device *** device_list, SANE_Bool local_on { DBG_HELPER_ARGS(dbg, "local_only = %s", local_only ? "true" : "false"); - /* hot-plug case : detection of newly connected scanners */ - sanei_usb_scan_devices (); - probe_genesys_devices (); + if (!is_testing_mode()) { + // hot-plug case : detection of newly connected scanners */ + sanei_usb_scan_devices(); + } + probe_genesys_devices(); s_sane_devices->clear(); s_sane_devices_data->clear(); @@ -4713,8 +4729,14 @@ void sane_get_devices_impl(const SANE_Device *** device_list, SANE_Bool local_on s_sane_devices_ptrs->reserve(s_devices->size() + 1); for (auto dev_it = s_devices->begin(); dev_it != s_devices->end();) { - present = false; - sanei_usb_find_devices(dev_it->vendorId, dev_it->productId, check_present); + + if (is_testing_mode()) { + present = true; + } else { + present = false; + sanei_usb_find_devices(dev_it->vendorId, dev_it->productId, check_present); + } + if (present) { s_sane_devices->emplace_back(); s_sane_devices_data->emplace_back(); @@ -4753,8 +4775,7 @@ static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle) /* devicename="" or devicename="genesys" are default values that use * first available device */ - if (devicename[0] && strcmp ("genesys", devicename) != 0) - { + if (devicename[0] && strcmp ("genesys", devicename) != 0) { /* search for the given devicename in the device list */ for (auto& d : *s_devices) { if (d.file_name == devicename) { @@ -4763,14 +4784,16 @@ static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle) } } - if (!dev) { + if (dev) { + DBG(DBG_info, "%s: found `%s' in devlist\n", __func__, dev->model->name); + } else if (is_testing_mode()) { + DBG(DBG_info, "%s: couldn't find `%s' in devlist, not attaching", __func__, devicename); + } else { DBG(DBG_info, "%s: couldn't find `%s' in devlist, trying attach\n", __func__, devicename); dbg.status("attach_device_by_name"); dev = attach_device_by_name(devicename, true); dbg.clear(); - } else { - DBG(DBG_info, "%s: found `%s' in devlist\n", __func__, dev->model->name); } } else { // empty devicename or "genesys" -> use first device @@ -4795,7 +4818,14 @@ static void sane_open_impl(SANE_String_Const devicename, SANE_Handle * handle) } dbg.vstatus("open device '%s'", dev->file_name.c_str()); - dev->interface = std::unique_ptr{new ScannerInterfaceUsb{dev}}; + + if (is_testing_mode()) { + auto interface = std::unique_ptr{new TestScannerInterface{dev}}; + interface->set_checkpoint_callback(get_testing_checkpoint_callback()); + dev->interface = std::move(interface); + } else { + dev->interface = std::unique_ptr{new ScannerInterfaceUsb{dev}}; + } dev->interface->get_usb_device().open(dev->file_name.c_str()); dbg.clear(); @@ -4890,7 +4920,7 @@ sane_close_impl(SANE_Handle handle) s->dev->cmd_set->save_power(s->dev, true); // here is the place to store calibration cache - if (s->dev->force_calibration == 0) { + if (s->dev->force_calibration == 0 && !is_testing_mode()) { catch_all_exceptions(__func__, [&](){ write_calibration(s->dev->calibration_cache, s->dev->calib_file); }); } diff --git a/backend/genesys/low.h b/backend/genesys/low.h index ab1e5b759..8807392f8 100644 --- a/backend/genesys/low.h +++ b/backend/genesys/low.h @@ -216,9 +216,9 @@ struct Genesys_USB_Device_Entry { {} // USB vendor identifier - unsigned vendor; + std::uint16_t vendor; // USB product identifier - unsigned product; + std::uint16_t product; // Scanner model information Genesys_Model model; }; diff --git a/backend/genesys/scanner_interface.h b/backend/genesys/scanner_interface.h index 13914b7a3..b0540301e 100644 --- a/backend/genesys/scanner_interface.h +++ b/backend/genesys/scanner_interface.h @@ -100,6 +100,8 @@ public: virtual void record_progress_message(const char* msg) = 0; virtual void record_key_value(const std::string& key, const std::string& value) = 0; + + virtual void test_checkpoint(const std::string& name) = 0; }; } // namespace genesys diff --git a/backend/genesys/scanner_interface_usb.cpp b/backend/genesys/scanner_interface_usb.cpp index 5ed712697..6e9c51b5c 100644 --- a/backend/genesys/scanner_interface_usb.cpp +++ b/backend/genesys/scanner_interface_usb.cpp @@ -500,4 +500,9 @@ void ScannerInterfaceUsb::record_key_value(const std::string& key, const std::st (void) value; } +void ScannerInterfaceUsb::test_checkpoint(const std::string& name) +{ + (void) name; +} + } // namespace genesys diff --git a/backend/genesys/scanner_interface_usb.h b/backend/genesys/scanner_interface_usb.h index 5e7957a1d..3cfd303f2 100644 --- a/backend/genesys/scanner_interface_usb.h +++ b/backend/genesys/scanner_interface_usb.h @@ -84,6 +84,8 @@ public: void record_key_value(const std::string& key, const std::string& value) override; + void test_checkpoint(const std::string& name) override; + private: Genesys_Device* dev_; UsbDevice usb_dev_; diff --git a/backend/genesys/test_scanner_interface.cpp b/backend/genesys/test_scanner_interface.cpp index a0403c03c..b594e6428 100644 --- a/backend/genesys/test_scanner_interface.cpp +++ b/backend/genesys/test_scanner_interface.cpp @@ -44,10 +44,56 @@ #define DEBUG_DECLARE_ONLY #include "test_scanner_interface.h" +#include "device.h" #include namespace genesys { +TestScannerInterface::TestScannerInterface(Genesys_Device* dev) : dev_{dev} +{ + // initialize status registers + if (dev_->model->asic_type == AsicType::GL124) { + write_register(0x101, 0x00); + } else { + write_register(0x41, 0x00); + } + if (dev_->model->asic_type == AsicType::GL841 || + dev_->model->asic_type == AsicType::GL843 || + dev_->model->asic_type == AsicType::GL845 || + dev_->model->asic_type == AsicType::GL846 || + dev_->model->asic_type == AsicType::GL847) + { + write_register(0x40, 0x00); + } + + // initialize other registers that we read on init + if (dev_->model->asic_type == AsicType::GL124) { + write_register(0x33, 0x00); + write_register(0xbd, 0x00); + write_register(0xbe, 0x00); + write_register(0x100, 0x00); + } + + if (dev_->model->asic_type == AsicType::GL845 || + dev_->model->asic_type == AsicType::GL846 || + dev_->model->asic_type == AsicType::GL847) + { + write_register(0xbd, 0x00); + write_register(0xbe, 0x00); + + write_register(0xd0, 0x00); + write_register(0xd1, 0x01); + write_register(0xd2, 0x02); + write_register(0xd3, 0x03); + write_register(0xd4, 0x04); + write_register(0xd5, 0x05); + write_register(0xd6, 0x06); + write_register(0xd7, 0x07); + write_register(0xd8, 0x08); + write_register(0xd9, 0x09); + } +} + TestScannerInterface::~TestScannerInterface() = default; bool TestScannerInterface::is_mock() const @@ -157,4 +203,16 @@ std::map& TestScannerInterface::recorded_key_values() return key_values_; } +void TestScannerInterface::test_checkpoint(const std::string& name) +{ + if (checkpoint_callback_) { + checkpoint_callback_(*dev_, *this, name); + } +} + +void TestScannerInterface::set_checkpoint_callback(TestCheckpointCallback callback) +{ + checkpoint_callback_ = callback; +} + } // namespace genesys diff --git a/backend/genesys/test_scanner_interface.h b/backend/genesys/test_scanner_interface.h index 731ab7504..709283565 100644 --- a/backend/genesys/test_scanner_interface.h +++ b/backend/genesys/test_scanner_interface.h @@ -47,6 +47,7 @@ #include "scanner_interface.h" #include "register_cache.h" #include "test_usb_device.h" +#include "test_settings.h" #include @@ -55,10 +56,15 @@ namespace genesys { class TestScannerInterface : public ScannerInterface { public: + TestScannerInterface(Genesys_Device* dev); + ~TestScannerInterface() override; bool is_mock() const override; + const RegisterCache& cached_regs() const { return cached_regs_; } + const RegisterCache& cached_fe_regs() const { return cached_fe_regs_; } + std::uint8_t read_register(std::uint16_t address) override; void write_register(std::uint16_t address, std::uint8_t value) override; void write_registers(const Genesys_Register_Set& regs) override; @@ -88,10 +94,18 @@ public: std::map& recorded_key_values(); + void test_checkpoint(const std::string& name) override; + + void set_checkpoint_callback(TestCheckpointCallback callback); + private: + Genesys_Device* dev_; + RegisterCache cached_regs_; RegisterCache cached_fe_regs_; TestUsbDevice usb_dev_; + + TestCheckpointCallback checkpoint_callback_; std::string last_progress_message_; std::map key_values_; }; diff --git a/backend/genesys/test_settings.cpp b/backend/genesys/test_settings.cpp new file mode 100644 index 000000000..425f09cc2 --- /dev/null +++ b/backend/genesys/test_settings.cpp @@ -0,0 +1,106 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "test_settings.h" + +namespace genesys { + +namespace { + +bool s_testing_mode = false; +std::uint16_t s_vendor_id = 0; +std::uint16_t s_product_id = 0; +TestCheckpointCallback s_checkpoint_callback; + +} // namespace + +bool is_testing_mode() +{ + return s_testing_mode; +} + +void disable_testing_mode() +{ + s_testing_mode = false; + s_vendor_id = 0; + s_product_id = 0; + +} + +void enable_testing_mode(std::uint16_t vendor_id, std::uint16_t product_id, + TestCheckpointCallback checkpoint_callback) +{ + s_testing_mode = true; + s_vendor_id = vendor_id; + s_product_id = product_id; + s_checkpoint_callback = checkpoint_callback; +} + +std::uint16_t get_testing_vendor_id() +{ + return s_vendor_id; +} + +std::uint16_t get_testing_product_id() +{ + return s_product_id; +} + +std::string get_testing_device_name() +{ + std::string name; + unsigned max_size = 50; + name.resize(max_size); + name.resize(std::snprintf(&name.front(), max_size, "test device:0x%04x:0x%04x", + s_vendor_id, s_product_id)); + return name; +} + +TestCheckpointCallback get_testing_checkpoint_callback() +{ + return s_checkpoint_callback; +} + +} // namespace genesys diff --git a/backend/genesys/test_settings.h b/backend/genesys/test_settings.h new file mode 100644 index 000000000..8ac03e010 --- /dev/null +++ b/backend/genesys/test_settings.h @@ -0,0 +1,70 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. +*/ + +#ifndef BACKEND_GENESYS_TEST_SETTINGS_H +#define BACKEND_GENESYS_TEST_SETTINGS_H + +#include "scanner_interface.h" +#include "register_cache.h" +#include "test_usb_device.h" +#include + +namespace genesys { + +using TestCheckpointCallback = std::function; + +bool is_testing_mode(); +void disable_testing_mode(); +void enable_testing_mode(std::uint16_t vendor_id, std::uint16_t product_id, + TestCheckpointCallback checkpoint_callback); +std::uint16_t get_testing_vendor_id(); +std::uint16_t get_testing_product_id(); +std::string get_testing_device_name(); +TestCheckpointCallback get_testing_checkpoint_callback(); + + +} // namespace genesys + +#endif // BACKEND_GENESYS_TEST_SETTINGS_H diff --git a/testsuite/backend/genesys/Makefile.am b/testsuite/backend/genesys/Makefile.am index c07d54979..525310e19 100644 --- a/testsuite/backend/genesys/Makefile.am +++ b/testsuite/backend/genesys/Makefile.am @@ -13,13 +13,13 @@ TEST_LDADD = \ ../../../backend/sane_strstatus.lo \ $(MATH_LIB) $(USB_LIBS) $(XML_LIBS) $(PTHREAD_LIBS) -check_PROGRAMS = genesys_tests -TESTS = $(check_PROGRAMS) +check_PROGRAMS = genesys_unit_tests genesys_session_config_tests +TESTS = genesys_unit_tests AM_CPPFLAGS += -I. -I$(srcdir) -I$(top_builddir)/include -I$(top_srcdir)/include $(USB_CFLAGS) \ - -DBACKEND_NAME=genesys + -DBACKEND_NAME=genesys -DTESTSUITE_BACKEND_GENESYS_SRCDIR=$(srcdir) -genesys_tests_SOURCES = tests.cpp tests.h \ +genesys_unit_tests_SOURCES = tests.cpp tests.h \ minigtest.cpp minigtest.h tests_printers.h \ tests_calibration.cpp \ tests_image.cpp \ @@ -27,4 +27,8 @@ genesys_tests_SOURCES = tests.cpp tests.h \ tests_row_buffer.cpp \ tests_utilities.cpp -genesys_tests_LDADD = $(TEST_LDADD) +genesys_unit_tests_LDADD = $(TEST_LDADD) + +genesys_session_config_tests_SOURCES = session_config_test.cpp + +genesys_session_config_tests_LDADD = $(TEST_LDADD) diff --git a/testsuite/backend/genesys/session_config_test.cpp b/testsuite/backend/genesys/session_config_test.cpp new file mode 100644 index 000000000..d1631c4eb --- /dev/null +++ b/testsuite/backend/genesys/session_config_test.cpp @@ -0,0 +1,490 @@ +/* sane - Scanner Access Now Easy. + + Copyright (C) 2019 Povilas Kanapickas + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. +*/ + +#define DEBUG_DECLARE_ONLY + +#include "../../../backend/genesys/device.h" +#include "../../../backend/genesys/enums.h" +#include "../../../backend/genesys/error.h" +#include "../../../backend/genesys/low.h" +#include "../../../backend/genesys/genesys.h" +#include "../../../backend/genesys/test_settings.h" +#include "../../../backend/genesys/test_scanner_interface.h" +#include "../../../backend/genesys/utilities.h" +#include "../../include/sane/saneopts.h" +#include "sys/stat.h" +#include +#include +#include +#include +#include +#include + +#define XSTR(s) STR(s) +#define STR(s) #s +#define CURR_SRCDIR XSTR(TESTSUITE_BACKEND_GENESYS_SRCDIR) + +struct TestConfig +{ + std::uint16_t vendor_id = 0; + std::uint16_t product_id = 0; + std::string model_name; + genesys::ScanMethod method = genesys::ScanMethod::FLATBED; + genesys::ScanColorMode color_mode = genesys::ScanColorMode::COLOR_SINGLE_PASS; + unsigned depth = 0; + unsigned resolution = 0; + + std::string name() const + { + std::stringstream out; + out << "capture_" << model_name + << '_' << method + << '_' << color_mode + << "_depth" << depth + << "_dpi" << resolution; + return out.str(); + } + +}; + +class SaneOptions +{ +public: + void fetch(SANE_Handle handle) + { + handle_ = handle; + options_.resize(1); + options_[0] = fetch_option(0); + + if (std::strcmp(options_[0].name, SANE_NAME_NUM_OPTIONS) != 0 || + options_[0].type != SANE_TYPE_INT) + { + throw std::runtime_error("Expected option number option"); + } + int option_count = 0; + TIE(sane_control_option(handle, 0, SANE_ACTION_GET_VALUE, &option_count, nullptr)); + + options_.resize(option_count); + for (int i = 0; i < option_count; ++i) { + options_[i] = fetch_option(i); + } + } + + void close() + { + handle_ = nullptr; + } + + bool get_value_bool(const std::string& name) const + { + auto i = find_option(name, SANE_TYPE_BOOL); + int value = 0; + TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr)); + return value; + } + + void set_value_bool(const std::string& name, bool value) + { + auto i = find_option(name, SANE_TYPE_BOOL); + int value_int = value; + TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr)); + } + + bool get_value_button(const std::string& name) const + { + auto i = find_option(name, SANE_TYPE_BUTTON); + int value = 0; + TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr)); + return value; + } + + void set_value_button(const std::string& name, bool value) + { + auto i = find_option(name, SANE_TYPE_BUTTON); + int value_int = value; + TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr)); + } + + int get_value_int(const std::string& name) const + { + auto i = find_option(name, SANE_TYPE_INT); + int value = 0; + TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr)); + return value; + } + + void set_value_int(const std::string& name, int value) + { + auto i = find_option(name, SANE_TYPE_INT); + TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value, nullptr)); + } + + float get_value_float(const std::string& name) const + { + auto i = find_option(name, SANE_TYPE_FIXED); + int value = 0; + TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr)); + return static_cast(SANE_UNFIX(value)); + } + + void set_value_float(const std::string& name, float value) + { + auto i = find_option(name, SANE_TYPE_FIXED); + int value_int = SANE_FIX(value); + TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr)); + } + + std::string get_value_string(const std::string& name) const + { + auto i = find_option(name, SANE_TYPE_STRING); + std::string value; + value.resize(options_[i].size + 1); + TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value.front(), nullptr)); + value.resize(std::strlen(&value.front())); + return value; + } + + void set_value_string(const std::string& name, const std::string& value) + { + auto i = find_option(name, SANE_TYPE_STRING); + TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, + const_cast(&value.front()), nullptr)); + } + +private: + SANE_Option_Descriptor fetch_option(int index) + { + const auto* option = sane_get_option_descriptor(handle_, index); + if (option == nullptr) { + throw std::runtime_error("Got nullptr option"); + } + return *option; + } + + std::size_t find_option(const std::string& name, SANE_Value_Type type) const + { + for (std::size_t i = 0; i < options_.size(); ++i) { + if (options_[i].name == name) { + if (options_[i].type != type) { + throw std::runtime_error("Option has incorrect type"); + } + return i; + } + } + throw std::runtime_error("Could not find option"); + } + + SANE_Handle handle_; + std::vector options_; +}; + + +void build_checkpoint(const genesys::Genesys_Device& dev, + genesys::TestScannerInterface& iface, + const std::string& checkpoint_name, + std::stringstream& out) +{ + out << "\n\n================\n" + << "Checkpoint: " << checkpoint_name << "\n" + << "================\n\n" + << "dev: " << genesys::format_indent_braced_list(4, dev) << "\n\n" + << "iface.cached_regs: " + << genesys::format_indent_braced_list(4, iface.cached_regs()) << "\n\n" + << "iface.cached_fe_regs: " + << genesys::format_indent_braced_list(4, iface.cached_fe_regs()) << "\n\n" + << "iface.last_progress_message: " << iface.last_progress_message() << "\n\n"; + if (iface.recorded_key_values().empty()) { + out << "iface.recorded_key_values: []\n"; + } else { + out << "iface.recorded_key_values: {\n"; + for (const auto& kv : iface.recorded_key_values()) { + out << " " << kv.first << " : " << kv.second << '\n'; + } + out << "}\n"; + } + out << "\n"; +} + +void run_single_test_scan(const TestConfig& config, std::stringstream& out) +{ + auto build_checkpoint_wrapper = [&](const genesys::Genesys_Device& dev, + genesys::TestScannerInterface& iface, + const std::string& checkpoint_name) + { + build_checkpoint(dev, iface, checkpoint_name, out); + }; + + genesys::enable_testing_mode(config.vendor_id, config.product_id, build_checkpoint_wrapper); + + SANE_Handle handle; + + TIE(sane_init(nullptr, nullptr)); + TIE(sane_open(genesys::get_testing_device_name().c_str(), &handle)); + + SaneOptions options; + options.fetch(handle); + + options.set_value_button("force-calibration", true); + options.set_value_string(SANE_NAME_SCAN_SOURCE, + genesys::scan_method_to_option_string(config.method)); + options.set_value_string(SANE_NAME_SCAN_MODE, + genesys::scan_color_mode_to_option_string(config.color_mode)); + if (config.color_mode != genesys::ScanColorMode::LINEART) { + options.set_value_int(SANE_NAME_BIT_DEPTH, config.depth); + } + options.set_value_int(SANE_NAME_SCAN_RESOLUTION, config.resolution); + options.close(); + + TIE(sane_start(handle)); + + SANE_Parameters params; + TIE(sane_get_parameters(handle, ¶ms)); + + int buffer_size = 1024 * 1024; + std::vector buffer; + buffer.resize(buffer_size); + + std::uint64_t total_data_size = std::uint64_t(params.bytes_per_line) * params.lines; + std::uint64_t total_got_data = 0; + + while (total_got_data < total_data_size) { + int ask_len = std::min(buffer_size, total_data_size - total_got_data); + + int got_data = 0; + auto status = sane_read(handle, buffer.data(), ask_len, &got_data); + total_got_data += got_data; + if (status == SANE_STATUS_EOF) { + break; + } + TIE(status); + } + + sane_cancel(handle); + sane_close(handle); + sane_exit(); + + genesys::disable_testing_mode(); +} + +std::string read_file_to_string(const std::string& path) +{ + std::ifstream in; + in.open(path); + if (!in.is_open()) { + return ""; + } + std::stringstream in_str; + in_str << in.rdbuf(); + return in_str.str(); +} + +void write_string_to_file(const std::string& path, const std::string& contents) +{ + std::ofstream out; + out.open(path); + if (!out.is_open()) { + throw std::runtime_error("Could not open output file: " + path); + } + out << contents; + out.close(); +} + +struct TestResult +{ + bool success = true; + TestConfig config; + std::string failure_message; +}; + +TestResult perform_single_test(const TestConfig& config, const std::string& check_directory, + const std::string& output_directory) +{ + TestResult test_result; + test_result.config = config; + + std::stringstream result_output_stream; + std::string exception_output; + try { + run_single_test_scan(config, result_output_stream); + } catch (const std::exception& exc) { + exception_output = std::string("got exception: ") + typeid(exc).name() + + " with message\n" + exc.what() + "\n"; + test_result.success = false; + test_result.failure_message += exception_output; + } catch (...) { + exception_output = "got unknown exception\n"; + test_result.success = false; + test_result.failure_message += exception_output; + } + auto result_output = result_output_stream.str(); + if (!exception_output.empty()) { + result_output += "\n\n" + exception_output; + } + + auto test_filename = config.name() + ".txt"; + auto expected_session_path = check_directory + "/" + test_filename; + auto current_session_path = output_directory + "/" + test_filename; + + auto expected_output = read_file_to_string(expected_session_path); + + bool has_output = !output_directory.empty(); + + if (has_output) { + mkdir(output_directory.c_str(), 0777); + // note that check_directory and output_directory may be the same, so make sure removal + // happens after the expected output has already been read. + std::remove(current_session_path.c_str()); + } + + if (expected_output.empty()) { + test_result.failure_message += "the expected data file does not exist\n"; + test_result.success = false; + } else if (expected_output != result_output) { + test_result.failure_message += "expected and current output are not equal\n"; + if (has_output) { + test_result.failure_message += "To examine, run:\ndiff -u \"" + current_session_path + + "\" \"" + expected_session_path + "\"\n"; + } + test_result.success = false; + } + + if (has_output) { + write_string_to_file(current_session_path, result_output); + } + return test_result; +} + +std::vector get_all_test_configs() +{ + genesys::genesys_init_usb_device_tables(); + + std::vector configs; + std::unordered_set model_names; + + for (const auto& usb_dev : *genesys::s_usb_devices) { + if (usb_dev.model.flags & GENESYS_FLAG_UNTESTED) { + continue; + } + if (model_names.find(usb_dev.model.name) != model_names.end()) { + continue; + } + model_names.insert(usb_dev.model.name); + + for (auto scan_mode : { genesys::ScanColorMode::LINEART, + genesys::ScanColorMode::GRAY, + genesys::ScanColorMode::COLOR_SINGLE_PASS }) { + + auto depth_values = usb_dev.model.bpp_gray_values; + if (scan_mode == genesys::ScanColorMode::COLOR_SINGLE_PASS) { + depth_values = usb_dev.model.bpp_color_values; + } + for (unsigned depth : depth_values) { + for (auto method_resolutions : usb_dev.model.resolutions) { + for (auto method : method_resolutions.methods) { + for (unsigned resolution : method_resolutions.get_resolutions()) { + TestConfig config; + config.vendor_id = usb_dev.vendor; + config.product_id = usb_dev.product; + config.model_name = usb_dev.model.name; + config.method = method; + config.depth = depth; + config.resolution = resolution; + config.color_mode = scan_mode; + configs.push_back(config); + } + } + } + } + } + } + return configs; +} + +void print_help() +{ + std::cerr << "Usage:\n" + << "session_config_test [--test={test_name}] {check_directory} [{output_directory}]\n" + << "session_config_test --help\n" + << "session_config_test --print_test_names\n"; +} + +int main(int argc, const char* argv[]) +{ + std::string check_directory; + std::string output_directory; + std::string test_name_filter; + bool print_test_names = false; + + for (int argi = 1; argi < argc; ++argi) { + std::string arg = argv[argi]; + if (arg.rfind("--test=", 0) == 0) { + test_name_filter = arg.substr(7); + } else if (arg == "-h" || arg == "--help") { + print_help(); + return 0; + } else if (arg == "--print_test_names") { + print_test_names = true; + } else if (check_directory.empty()) { + check_directory = arg; + } else if (output_directory.empty()) { + output_directory = arg; + } + } + + auto configs = get_all_test_configs(); + + if (print_test_names) { + for (const auto& config : configs) { + std::cout << config.name() << "\n"; + } + return 0; + } + + if (check_directory.empty()) { + print_help(); + return 1; + } + + bool test_success = true; + for (unsigned i = 0; i < configs.size(); ++i) { + const auto& config = configs[i]; + + if (!test_name_filter.empty() && config.name() != test_name_filter) { + continue; + } + + auto result = perform_single_test(config, check_directory, output_directory); + std::cerr << "(" << i << "/" << configs.size() << "): " + << (result.success ? "SUCCESS: " : "FAIL: ") + << result.config.name() << "\n"; + if (!result.success) { + std::cerr << result.failure_message; + } + + test_success &= result.success; + } + + if (!test_success) { + return 1; + } + return 0; +}