• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
44 struct 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 
nameTestConfig55     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 
68 class SaneOptions
69 {
70 public:
fetch(SANE_Handle handle)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 
close()91     void close()
92     {
93         handle_ = nullptr;
94     }
95 
get_value_bool(const std::string & name) const96     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 
set_value_bool(const std::string & name,bool value)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 
get_value_button(const std::string & name) const111     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 
set_value_button(const std::string & name,bool value)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 
get_value_int(const std::string & name) const126     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 
set_value_int(const std::string & name,int value)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 
get_value_float(const std::string & name) const140     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 
set_value_float(const std::string & name,float value)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 
get_value_string(const std::string & name) const155     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 
set_value_string(const std::string & name,const std::string & value)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 
172 private:
fetch_option(int index)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 
find_option(const std::string & name,SANE_Value_Type type) const182     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 
print_params(const SANE_Parameters & params,std::stringstream & out)200 void 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 
print_checkpoint(const genesys::Genesys_Device & dev,genesys::TestScannerInterface & iface,const std::string & checkpoint_name,std::stringstream & out)212 void 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 
run_single_test_scan(const TestConfig & config,std::stringstream & out)251 void 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 
read_file_to_string(const std::string & path)315 std::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 
write_string_to_file(const std::string & path,const std::string & contents)327 void 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 
338 struct TestResult
339 {
340     bool success = true;
341     TestConfig config;
342     std::string failure_message;
343 };
344 
perform_single_test(const TestConfig & config,const std::string & check_directory,const std::string & output_directory)345 TestResult 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 
get_all_test_configs()403 std::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 
print_help()455 void 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 
main(int argc,const char * argv[])463 int 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