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, ¶ms));
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