1/* sane - Scanner Access Now Easy.
2
3   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
4
5   This file is part of the SANE package.
6
7   This program is free software; you can redistribute it and/or
8   modify it under the terms of the GNU General Public License as
9   published by the Free Software Foundation; either version 2 of the
10   License, or (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program.  If not, see <https://www.gnu.org/licenses/>.
19*/
20
21#define DEBUG_DECLARE_ONLY
22
23#include "../../../backend/genesys/device.h"
24#include "../../../backend/genesys/enums.h"
25#include "../../../backend/genesys/error.h"
26#include "../../../backend/genesys/low.h"
27#include "../../../backend/genesys/genesys.h"
28#include "../../../backend/genesys/test_settings.h"
29#include "../../../backend/genesys/test_scanner_interface.h"
30#include "../../../backend/genesys/utilities.h"
31#include "../../../include/sane/saneopts.h"
32#include "sys/stat.h"
33#include <cstdio>
34#include <cstring>
35#include <fstream>
36#include <sstream>
37#include <string>
38#include <unordered_set>
39
40#define XSTR(s) STR(s)
41#define STR(s) #s
42#define CURR_SRCDIR XSTR(TESTSUITE_BACKEND_GENESYS_SRCDIR)
43
44struct TestConfig
45{
46    std::uint16_t vendor_id = 0;
47    std::uint16_t product_id = 0;
48    std::uint16_t bcd_device = 0;
49    std::string model_name;
50    genesys::ScanMethod method = genesys::ScanMethod::FLATBED;
51    genesys::ScanColorMode color_mode = genesys::ScanColorMode::COLOR_SINGLE_PASS;
52    unsigned depth = 0;
53    unsigned resolution = 0;
54
55    std::string name() const
56    {
57        std::stringstream out;
58        out << "capture_" << model_name
59            << '_' << method
60            << '_' << color_mode
61            << "_depth" << depth
62            << "_dpi" << resolution;
63        return out.str();
64    }
65
66};
67
68class SaneOptions
69{
70public:
71    void fetch(SANE_Handle handle)
72    {
73        handle_ = handle;
74        options_.resize(1);
75        options_[0] = fetch_option(0);
76
77        if (std::strcmp(options_[0].name, SANE_NAME_NUM_OPTIONS) != 0 ||
78            options_[0].type != SANE_TYPE_INT)
79        {
80            throw std::runtime_error("Expected option number option");
81        }
82        int option_count = 0;
83        TIE(sane_control_option(handle, 0, SANE_ACTION_GET_VALUE, &option_count, nullptr));
84
85        options_.resize(option_count);
86        for (int i = 0; i < option_count; ++i) {
87            options_[i] = fetch_option(i);
88        }
89    }
90
91    void close()
92    {
93        handle_ = nullptr;
94    }
95
96    bool get_value_bool(const std::string& name) const
97    {
98        auto i = find_option(name, SANE_TYPE_BOOL);
99        int value = 0;
100        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
101        return value;
102    }
103
104    void set_value_bool(const std::string& name, bool value)
105    {
106        auto i = find_option(name, SANE_TYPE_BOOL);
107        int value_int = value;
108        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
109    }
110
111    bool get_value_button(const std::string& name) const
112    {
113        auto i = find_option(name, SANE_TYPE_BUTTON);
114        int value = 0;
115        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
116        return value;
117    }
118
119    void set_value_button(const std::string& name, bool value)
120    {
121        auto i = find_option(name, SANE_TYPE_BUTTON);
122        int value_int = value;
123        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
124    }
125
126    int get_value_int(const std::string& name) const
127    {
128        auto i = find_option(name, SANE_TYPE_INT);
129        int value = 0;
130        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
131        return value;
132    }
133
134    void set_value_int(const std::string& name, int value)
135    {
136        auto i = find_option(name, SANE_TYPE_INT);
137        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value, nullptr));
138    }
139
140    float get_value_float(const std::string& name) const
141    {
142        auto i = find_option(name, SANE_TYPE_FIXED);
143        int value = 0;
144        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
145        return genesys::fixed_to_float(value);
146    }
147
148    void set_value_float(const std::string& name, float value)
149    {
150        auto i = find_option(name, SANE_TYPE_FIXED);
151        int value_int = SANE_FIX(value);
152        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
153    }
154
155    std::string get_value_string(const std::string& name) const
156    {
157        auto i = find_option(name, SANE_TYPE_STRING);
158        std::string value;
159        value.resize(options_[i].size + 1);
160        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value.front(), nullptr));
161        value.resize(std::strlen(&value.front()));
162        return value;
163    }
164
165    void set_value_string(const std::string& name, const std::string& value)
166    {
167        auto i = find_option(name, SANE_TYPE_STRING);
168        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE,
169                                const_cast<char*>(&value.front()), nullptr));
170    }
171
172private:
173    SANE_Option_Descriptor fetch_option(int index)
174    {
175        const auto* option = sane_get_option_descriptor(handle_, index);
176        if (option == nullptr) {
177            throw std::runtime_error("Got nullptr option");
178        }
179        return *option;
180    }
181
182    std::size_t find_option(const std::string& name, SANE_Value_Type type) const
183    {
184        for (std::size_t i = 0; i < options_.size(); ++i) {
185            if (options_[i].name == name) {
186                if (options_[i].type != type) {
187                    throw std::runtime_error("Option has incorrect type");
188                }
189                return i;
190            }
191        }
192        throw std::runtime_error("Could not find option");
193    }
194
195    SANE_Handle handle_;
196    std::vector<SANE_Option_Descriptor> options_;
197};
198
199
200void print_params(const SANE_Parameters& params, std::stringstream& out)
201{
202    out << "\n\n================\n"
203        << "Scan params:\n"
204        << "format: " << params.format << "\n"
205        << "last_frame: " << params.last_frame << "\n"
206        << "bytes_per_line: " << params.bytes_per_line << "\n"
207        << "pixels_per_line: " << params.pixels_per_line << "\n"
208        << "lines: " << params.lines << "\n"
209        << "depth: " << params.depth << "\n";
210}
211
212void print_checkpoint(const genesys::Genesys_Device& dev,
213                      genesys::TestScannerInterface& iface,
214                      const std::string& checkpoint_name,
215                      std::stringstream& out)
216{
217    out << "\n\n================\n"
218        << "Checkpoint: " << checkpoint_name << "\n"
219        << "================\n\n"
220        << "dev: " << genesys::format_indent_braced_list(4, dev) << "\n\n"
221        << "iface.cached_regs: "
222        << genesys::format_indent_braced_list(4, iface.cached_regs()) << "\n\n"
223        << "iface.cached_fe_regs: "
224        << genesys::format_indent_braced_list(4, iface.cached_fe_regs()) << "\n\n"
225        << "iface.last_progress_message: " << iface.last_progress_message() << "\n\n";
226    out << "iface.slope_tables: {\n";
227    for (const auto& kv : iface.recorded_slope_tables()) {
228        out << "    " << kv.first << ": {";
229        for (unsigned i = 0; i < kv.second.size(); ++i) {
230            if (i % 10 == 0) {
231                out << "\n       ";
232            }
233            out << ' ' << kv.second[i];
234        }
235        out << "\n    }\n";
236    }
237    out << "}\n";
238    if (iface.recorded_key_values().empty()) {
239        out << "iface.recorded_key_values: []\n";
240    } else {
241        out << "iface.recorded_key_values: {\n";
242        for (const auto& kv : iface.recorded_key_values()) {
243            out << "    " << kv.first << " : " << kv.second << '\n';
244        }
245        out << "}\n";
246    }
247    iface.recorded_key_values().clear();
248    out << "\n";
249}
250
251void run_single_test_scan(const TestConfig& config, std::stringstream& out)
252{
253    auto print_checkpoint_wrapper = [&](const genesys::Genesys_Device& dev,
254                                        genesys::TestScannerInterface& iface,
255                                        const std::string& checkpoint_name)
256    {
257        print_checkpoint(dev, iface, checkpoint_name, out);
258    };
259
260    genesys::enable_testing_mode(config.vendor_id, config.product_id, config.bcd_device,
261                                 print_checkpoint_wrapper);
262
263    SANE_Handle handle;
264
265    TIE(sane_init(nullptr, nullptr));
266    TIE(sane_open(genesys::get_testing_device_name().c_str(), &handle));
267
268    SaneOptions options;
269    options.fetch(handle);
270
271    options.set_value_button("force-calibration", true);
272    options.set_value_string(SANE_NAME_SCAN_SOURCE,
273                             genesys::scan_method_to_option_string(config.method));
274    options.set_value_string(SANE_NAME_SCAN_MODE,
275                             genesys::scan_color_mode_to_option_string(config.color_mode));
276    if (config.color_mode != genesys::ScanColorMode::LINEART) {
277        options.set_value_int(SANE_NAME_BIT_DEPTH, config.depth);
278    }
279    options.set_value_int(SANE_NAME_SCAN_RESOLUTION, config.resolution);
280    options.close();
281
282    TIE(sane_start(handle));
283
284    SANE_Parameters params;
285    TIE(sane_get_parameters(handle, &params));
286
287    print_params(params, out);
288
289    int buffer_size = 1024 * 1024;
290    std::vector<std::uint8_t> buffer;
291    buffer.resize(buffer_size);
292
293    std::uint64_t total_data_size = std::uint64_t(params.bytes_per_line) * params.lines;
294    std::uint64_t total_got_data = 0;
295
296    while (total_got_data < total_data_size) {
297        int ask_len = std::min<std::size_t>(buffer_size, total_data_size - total_got_data);
298
299        int got_data = 0;
300        auto status = sane_read(handle, buffer.data(), ask_len, &got_data);
301        total_got_data += got_data;
302        if (status == SANE_STATUS_EOF) {
303            break;
304        }
305        TIE(status);
306    }
307
308    sane_cancel(handle);
309    sane_close(handle);
310    sane_exit();
311
312    genesys::disable_testing_mode();
313}
314
315std::string read_file_to_string(const std::string& path)
316{
317    std::ifstream in;
318    in.open(path);
319    if (!in.is_open()) {
320        return "";
321    }
322    std::stringstream in_str;
323    in_str << in.rdbuf();
324    return in_str.str();
325}
326
327void write_string_to_file(const std::string& path, const std::string& contents)
328{
329    std::ofstream out;
330    out.open(path);
331    if (!out.is_open()) {
332        throw std::runtime_error("Could not open output file: " + path);
333    }
334    out << contents;
335    out.close();
336}
337
338struct TestResult
339{
340    bool success = true;
341    TestConfig config;
342    std::string failure_message;
343};
344
345TestResult perform_single_test(const TestConfig& config, const std::string& check_directory,
346                               const std::string& output_directory)
347{
348    TestResult test_result;
349    test_result.config = config;
350
351    std::stringstream result_output_stream;
352    std::string exception_output;
353    try {
354        run_single_test_scan(config, result_output_stream);
355    } catch (const std::exception& exc) {
356        exception_output = std::string("got exception: ") + typeid(exc).name() +
357                           " with message\n" + exc.what() + "\n";
358        test_result.success = false;
359        test_result.failure_message += exception_output;
360    } catch (...) {
361        exception_output = "got unknown exception\n";
362        test_result.success = false;
363        test_result.failure_message += exception_output;
364    }
365    auto result_output = result_output_stream.str();
366    if (!exception_output.empty()) {
367        result_output += "\n\n" + exception_output;
368    }
369
370    auto test_filename = config.name() + ".txt";
371    auto expected_session_path = check_directory + "/" + test_filename;
372    auto current_session_path = output_directory + "/" + test_filename;
373
374    auto expected_output = read_file_to_string(expected_session_path);
375
376    bool has_output = !output_directory.empty();
377
378    if (has_output) {
379        mkdir(output_directory.c_str(), 0777);
380        // note that check_directory and output_directory may be the same, so make sure removal
381        // happens after the expected output has already been read.
382        std::remove(current_session_path.c_str());
383    }
384
385    if (expected_output.empty()) {
386        test_result.failure_message += "the expected data file does not exist\n";
387        test_result.success = false;
388    } else if (expected_output != result_output) {
389        test_result.failure_message += "expected and current output are not equal\n";
390        if (has_output) {
391            test_result.failure_message += "To examine, run:\ndiff -u \"" + current_session_path +
392                                           "\" \"" + expected_session_path + "\"\n";
393        }
394        test_result.success = false;
395    }
396
397    if (has_output) {
398        write_string_to_file(current_session_path, result_output);
399    }
400    return test_result;
401}
402
403std::vector<TestConfig> get_all_test_configs()
404{
405    genesys::genesys_init_usb_device_tables();
406    genesys::genesys_init_sensor_tables();
407    genesys::verify_usb_device_tables();
408    genesys::verify_sensor_tables();
409
410    std::vector<TestConfig> configs;
411    std::unordered_set<std::string> model_names;
412
413    for (const auto& usb_dev : *genesys::s_usb_devices) {
414
415        const auto& model = usb_dev.model();
416
417        if (genesys::has_flag(model.flags, genesys::ModelFlag::UNTESTED)) {
418            continue;
419        }
420        if (model_names.find(model.name) != model_names.end()) {
421            continue;
422        }
423        model_names.insert(model.name);
424
425        for (auto scan_mode : { genesys::ScanColorMode::GRAY,
426                                genesys::ScanColorMode::COLOR_SINGLE_PASS }) {
427
428            auto depth_values = model.bpp_gray_values;
429            if (scan_mode == genesys::ScanColorMode::COLOR_SINGLE_PASS) {
430                depth_values = model.bpp_color_values;
431            }
432            for (unsigned depth : depth_values) {
433                for (auto method_resolutions : model.resolutions) {
434                    for (auto method : method_resolutions.methods) {
435                        for (unsigned resolution : method_resolutions.get_resolutions()) {
436                            TestConfig config;
437                            config.vendor_id = usb_dev.vendor_id();
438                            config.product_id = usb_dev.product_id();
439                            config.bcd_device = usb_dev.bcd_device();
440                            config.model_name = model.name;
441                            config.method = method;
442                            config.depth = depth;
443                            config.resolution = resolution;
444                            config.color_mode = scan_mode;
445                            configs.push_back(config);
446                        }
447                    }
448                }
449            }
450        }
451    }
452    return configs;
453}
454
455void print_help()
456{
457    std::cerr << "Usage:\n"
458              << "session_config_test [--test={test_name}] {check_directory} [{output_directory}]\n"
459              << "session_config_test --help\n"
460              << "session_config_test --print_test_names\n";
461}
462
463int main(int argc, const char* argv[])
464{
465    std::string check_directory;
466    std::string output_directory;
467    std::string test_name_filter;
468    bool print_test_names = false;
469
470    for (int argi = 1; argi < argc; ++argi) {
471        std::string arg = argv[argi];
472        if (arg.rfind("--test=", 0) == 0) {
473            test_name_filter = arg.substr(7);
474        } else if (arg == "-h" || arg == "--help") {
475            print_help();
476            return 0;
477        } else if (arg == "--print_test_names") {
478            print_test_names = true;
479        } else if (check_directory.empty()) {
480            check_directory = arg;
481        } else if (output_directory.empty()) {
482            output_directory = arg;
483        }
484    }
485
486    auto configs = get_all_test_configs();
487
488    if (print_test_names) {
489        for (const auto& config : configs) {
490            std::cout << config.name() << "\n";
491        }
492        return 0;
493    }
494
495    if (check_directory.empty()) {
496        print_help();
497        return 1;
498    }
499
500    bool test_success = true;
501    for (unsigned i = 0; i < configs.size(); ++i) {
502        const auto& config = configs[i];
503
504        if (!test_name_filter.empty() && config.name() != test_name_filter) {
505            continue;
506        }
507
508        auto result = perform_single_test(config, check_directory, output_directory);
509        std::cerr << "(" << i << "/" << configs.size() << "): "
510                  << (result.success ? "SUCCESS: " : "FAIL: ")
511                  << result.config.name() << "\n";
512        if (!result.success) {
513            std::cerr << result.failure_message;
514        }
515
516        test_success &= result.success;
517    }
518
519    if (!test_success) {
520        return 1;
521    }
522    return 0;
523}
524