diff options
author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2020-03-30 21:30:45 +0200 |
---|---|---|
committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2020-03-30 21:30:45 +0200 |
commit | ee770c2346eb37e0dcb8b6cf3eaacf3d8efd6bbc (patch) | |
tree | 58f05092be1a17a939e861f8cadcda1b6ca2ecef /testsuite/backend/genesys/session_config_test.cpp | |
parent | 0da9e21872802cfc6e975b1ebaf9efb9e5934d84 (diff) | |
parent | fef76e17ed4c607ea73b81279f9ef1d7121be900 (diff) |
Merge branch 'release/experimental/1.0.29-1_experimental1'experimental/1.0.29-1_experimental1
Diffstat (limited to 'testsuite/backend/genesys/session_config_test.cpp')
-rw-r--r-- | testsuite/backend/genesys/session_config_test.cpp | 503 |
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, ¶ms)); + + 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; +} |