summaryrefslogtreecommitdiff
path: root/testsuite/backend/genesys/session_config_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'testsuite/backend/genesys/session_config_test.cpp')
-rw-r--r--testsuite/backend/genesys/session_config_test.cpp503
1 files changed, 503 insertions, 0 deletions
diff --git a/testsuite/backend/genesys/session_config_test.cpp b/testsuite/backend/genesys/session_config_test.cpp
new file mode 100644
index 0000000..72043bb
--- /dev/null
+++ b/testsuite/backend/genesys/session_config_test.cpp
@@ -0,0 +1,503 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
+
+ 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 <cstdio>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+
+#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<float>(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<char*>(&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<SANE_Option_Descriptor> 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";
+ out << "iface.slope_tables: {\n";
+ for (const auto& kv : iface.recorded_slope_tables()) {
+ out << " " << kv.first << ": {";
+ for (unsigned i = 0; i < kv.second.size(); ++i) {
+ if (i % 10 == 0) {
+ out << "\n ";
+ }
+ out << ' ' << kv.second[i];
+ }
+ out << "\n }\n";
+ }
+ out << "}\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";
+ }
+ iface.recorded_key_values().clear();
+ 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, &params));
+
+ int buffer_size = 1024 * 1024;
+ std::vector<std::uint8_t> 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<std::size_t>(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<TestConfig> get_all_test_configs()
+{
+ genesys::genesys_init_usb_device_tables();
+
+ std::vector<TestConfig> configs;
+ std::unordered_set<std::string> 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;
+}