1141cc406Sopenharmony_ci/* sane - Scanner Access Now Easy.
2141cc406Sopenharmony_ci
3141cc406Sopenharmony_ci   Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt>
4141cc406Sopenharmony_ci
5141cc406Sopenharmony_ci   This file is part of the SANE package.
6141cc406Sopenharmony_ci
7141cc406Sopenharmony_ci   This program is free software; you can redistribute it and/or
8141cc406Sopenharmony_ci   modify it under the terms of the GNU General Public License as
9141cc406Sopenharmony_ci   published by the Free Software Foundation; either version 2 of the
10141cc406Sopenharmony_ci   License, or (at your option) any later version.
11141cc406Sopenharmony_ci
12141cc406Sopenharmony_ci   This program is distributed in the hope that it will be useful, but
13141cc406Sopenharmony_ci   WITHOUT ANY WARRANTY; without even the implied warranty of
14141cc406Sopenharmony_ci   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15141cc406Sopenharmony_ci   General Public License for more details.
16141cc406Sopenharmony_ci
17141cc406Sopenharmony_ci   You should have received a copy of the GNU General Public License
18141cc406Sopenharmony_ci   along with this program.  If not, see <https://www.gnu.org/licenses/>.
19141cc406Sopenharmony_ci*/
20141cc406Sopenharmony_ci
21141cc406Sopenharmony_ci#define DEBUG_DECLARE_ONLY
22141cc406Sopenharmony_ci
23141cc406Sopenharmony_ci#include "../../../backend/genesys/device.h"
24141cc406Sopenharmony_ci#include "../../../backend/genesys/enums.h"
25141cc406Sopenharmony_ci#include "../../../backend/genesys/error.h"
26141cc406Sopenharmony_ci#include "../../../backend/genesys/low.h"
27141cc406Sopenharmony_ci#include "../../../backend/genesys/genesys.h"
28141cc406Sopenharmony_ci#include "../../../backend/genesys/test_settings.h"
29141cc406Sopenharmony_ci#include "../../../backend/genesys/test_scanner_interface.h"
30141cc406Sopenharmony_ci#include "../../../backend/genesys/utilities.h"
31141cc406Sopenharmony_ci#include "../../../include/sane/saneopts.h"
32141cc406Sopenharmony_ci#include "sys/stat.h"
33141cc406Sopenharmony_ci#include <cstdio>
34141cc406Sopenharmony_ci#include <cstring>
35141cc406Sopenharmony_ci#include <fstream>
36141cc406Sopenharmony_ci#include <sstream>
37141cc406Sopenharmony_ci#include <string>
38141cc406Sopenharmony_ci#include <unordered_set>
39141cc406Sopenharmony_ci
40141cc406Sopenharmony_ci#define XSTR(s) STR(s)
41141cc406Sopenharmony_ci#define STR(s) #s
42141cc406Sopenharmony_ci#define CURR_SRCDIR XSTR(TESTSUITE_BACKEND_GENESYS_SRCDIR)
43141cc406Sopenharmony_ci
44141cc406Sopenharmony_cistruct TestConfig
45141cc406Sopenharmony_ci{
46141cc406Sopenharmony_ci    std::uint16_t vendor_id = 0;
47141cc406Sopenharmony_ci    std::uint16_t product_id = 0;
48141cc406Sopenharmony_ci    std::uint16_t bcd_device = 0;
49141cc406Sopenharmony_ci    std::string model_name;
50141cc406Sopenharmony_ci    genesys::ScanMethod method = genesys::ScanMethod::FLATBED;
51141cc406Sopenharmony_ci    genesys::ScanColorMode color_mode = genesys::ScanColorMode::COLOR_SINGLE_PASS;
52141cc406Sopenharmony_ci    unsigned depth = 0;
53141cc406Sopenharmony_ci    unsigned resolution = 0;
54141cc406Sopenharmony_ci
55141cc406Sopenharmony_ci    std::string name() const
56141cc406Sopenharmony_ci    {
57141cc406Sopenharmony_ci        std::stringstream out;
58141cc406Sopenharmony_ci        out << "capture_" << model_name
59141cc406Sopenharmony_ci            << '_' << method
60141cc406Sopenharmony_ci            << '_' << color_mode
61141cc406Sopenharmony_ci            << "_depth" << depth
62141cc406Sopenharmony_ci            << "_dpi" << resolution;
63141cc406Sopenharmony_ci        return out.str();
64141cc406Sopenharmony_ci    }
65141cc406Sopenharmony_ci
66141cc406Sopenharmony_ci};
67141cc406Sopenharmony_ci
68141cc406Sopenharmony_ciclass SaneOptions
69141cc406Sopenharmony_ci{
70141cc406Sopenharmony_cipublic:
71141cc406Sopenharmony_ci    void fetch(SANE_Handle handle)
72141cc406Sopenharmony_ci    {
73141cc406Sopenharmony_ci        handle_ = handle;
74141cc406Sopenharmony_ci        options_.resize(1);
75141cc406Sopenharmony_ci        options_[0] = fetch_option(0);
76141cc406Sopenharmony_ci
77141cc406Sopenharmony_ci        if (std::strcmp(options_[0].name, SANE_NAME_NUM_OPTIONS) != 0 ||
78141cc406Sopenharmony_ci            options_[0].type != SANE_TYPE_INT)
79141cc406Sopenharmony_ci        {
80141cc406Sopenharmony_ci            throw std::runtime_error("Expected option number option");
81141cc406Sopenharmony_ci        }
82141cc406Sopenharmony_ci        int option_count = 0;
83141cc406Sopenharmony_ci        TIE(sane_control_option(handle, 0, SANE_ACTION_GET_VALUE, &option_count, nullptr));
84141cc406Sopenharmony_ci
85141cc406Sopenharmony_ci        options_.resize(option_count);
86141cc406Sopenharmony_ci        for (int i = 0; i < option_count; ++i) {
87141cc406Sopenharmony_ci            options_[i] = fetch_option(i);
88141cc406Sopenharmony_ci        }
89141cc406Sopenharmony_ci    }
90141cc406Sopenharmony_ci
91141cc406Sopenharmony_ci    void close()
92141cc406Sopenharmony_ci    {
93141cc406Sopenharmony_ci        handle_ = nullptr;
94141cc406Sopenharmony_ci    }
95141cc406Sopenharmony_ci
96141cc406Sopenharmony_ci    bool get_value_bool(const std::string& name) const
97141cc406Sopenharmony_ci    {
98141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_BOOL);
99141cc406Sopenharmony_ci        int value = 0;
100141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
101141cc406Sopenharmony_ci        return value;
102141cc406Sopenharmony_ci    }
103141cc406Sopenharmony_ci
104141cc406Sopenharmony_ci    void set_value_bool(const std::string& name, bool value)
105141cc406Sopenharmony_ci    {
106141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_BOOL);
107141cc406Sopenharmony_ci        int value_int = value;
108141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
109141cc406Sopenharmony_ci    }
110141cc406Sopenharmony_ci
111141cc406Sopenharmony_ci    bool get_value_button(const std::string& name) const
112141cc406Sopenharmony_ci    {
113141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_BUTTON);
114141cc406Sopenharmony_ci        int value = 0;
115141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
116141cc406Sopenharmony_ci        return value;
117141cc406Sopenharmony_ci    }
118141cc406Sopenharmony_ci
119141cc406Sopenharmony_ci    void set_value_button(const std::string& name, bool value)
120141cc406Sopenharmony_ci    {
121141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_BUTTON);
122141cc406Sopenharmony_ci        int value_int = value;
123141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
124141cc406Sopenharmony_ci    }
125141cc406Sopenharmony_ci
126141cc406Sopenharmony_ci    int get_value_int(const std::string& name) const
127141cc406Sopenharmony_ci    {
128141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_INT);
129141cc406Sopenharmony_ci        int value = 0;
130141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
131141cc406Sopenharmony_ci        return value;
132141cc406Sopenharmony_ci    }
133141cc406Sopenharmony_ci
134141cc406Sopenharmony_ci    void set_value_int(const std::string& name, int value)
135141cc406Sopenharmony_ci    {
136141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_INT);
137141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value, nullptr));
138141cc406Sopenharmony_ci    }
139141cc406Sopenharmony_ci
140141cc406Sopenharmony_ci    float get_value_float(const std::string& name) const
141141cc406Sopenharmony_ci    {
142141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_FIXED);
143141cc406Sopenharmony_ci        int value = 0;
144141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value, nullptr));
145141cc406Sopenharmony_ci        return genesys::fixed_to_float(value);
146141cc406Sopenharmony_ci    }
147141cc406Sopenharmony_ci
148141cc406Sopenharmony_ci    void set_value_float(const std::string& name, float value)
149141cc406Sopenharmony_ci    {
150141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_FIXED);
151141cc406Sopenharmony_ci        int value_int = SANE_FIX(value);
152141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE, &value_int, nullptr));
153141cc406Sopenharmony_ci    }
154141cc406Sopenharmony_ci
155141cc406Sopenharmony_ci    std::string get_value_string(const std::string& name) const
156141cc406Sopenharmony_ci    {
157141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_STRING);
158141cc406Sopenharmony_ci        std::string value;
159141cc406Sopenharmony_ci        value.resize(options_[i].size + 1);
160141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_GET_VALUE, &value.front(), nullptr));
161141cc406Sopenharmony_ci        value.resize(std::strlen(&value.front()));
162141cc406Sopenharmony_ci        return value;
163141cc406Sopenharmony_ci    }
164141cc406Sopenharmony_ci
165141cc406Sopenharmony_ci    void set_value_string(const std::string& name, const std::string& value)
166141cc406Sopenharmony_ci    {
167141cc406Sopenharmony_ci        auto i = find_option(name, SANE_TYPE_STRING);
168141cc406Sopenharmony_ci        TIE(sane_control_option(handle_, i, SANE_ACTION_SET_VALUE,
169141cc406Sopenharmony_ci                                const_cast<char*>(&value.front()), nullptr));
170141cc406Sopenharmony_ci    }
171141cc406Sopenharmony_ci
172141cc406Sopenharmony_ciprivate:
173141cc406Sopenharmony_ci    SANE_Option_Descriptor fetch_option(int index)
174141cc406Sopenharmony_ci    {
175141cc406Sopenharmony_ci        const auto* option = sane_get_option_descriptor(handle_, index);
176141cc406Sopenharmony_ci        if (option == nullptr) {
177141cc406Sopenharmony_ci            throw std::runtime_error("Got nullptr option");
178141cc406Sopenharmony_ci        }
179141cc406Sopenharmony_ci        return *option;
180141cc406Sopenharmony_ci    }
181141cc406Sopenharmony_ci
182141cc406Sopenharmony_ci    std::size_t find_option(const std::string& name, SANE_Value_Type type) const
183141cc406Sopenharmony_ci    {
184141cc406Sopenharmony_ci        for (std::size_t i = 0; i < options_.size(); ++i) {
185141cc406Sopenharmony_ci            if (options_[i].name == name) {
186141cc406Sopenharmony_ci                if (options_[i].type != type) {
187141cc406Sopenharmony_ci                    throw std::runtime_error("Option has incorrect type");
188141cc406Sopenharmony_ci                }
189141cc406Sopenharmony_ci                return i;
190141cc406Sopenharmony_ci            }
191141cc406Sopenharmony_ci        }
192141cc406Sopenharmony_ci        throw std::runtime_error("Could not find option");
193141cc406Sopenharmony_ci    }
194141cc406Sopenharmony_ci
195141cc406Sopenharmony_ci    SANE_Handle handle_;
196141cc406Sopenharmony_ci    std::vector<SANE_Option_Descriptor> options_;
197141cc406Sopenharmony_ci};
198141cc406Sopenharmony_ci
199141cc406Sopenharmony_ci
200141cc406Sopenharmony_civoid print_params(const SANE_Parameters& params, std::stringstream& out)
201141cc406Sopenharmony_ci{
202141cc406Sopenharmony_ci    out << "\n\n================\n"
203141cc406Sopenharmony_ci        << "Scan params:\n"
204141cc406Sopenharmony_ci        << "format: " << params.format << "\n"
205141cc406Sopenharmony_ci        << "last_frame: " << params.last_frame << "\n"
206141cc406Sopenharmony_ci        << "bytes_per_line: " << params.bytes_per_line << "\n"
207141cc406Sopenharmony_ci        << "pixels_per_line: " << params.pixels_per_line << "\n"
208141cc406Sopenharmony_ci        << "lines: " << params.lines << "\n"
209141cc406Sopenharmony_ci        << "depth: " << params.depth << "\n";
210141cc406Sopenharmony_ci}
211141cc406Sopenharmony_ci
212141cc406Sopenharmony_civoid print_checkpoint(const genesys::Genesys_Device& dev,
213141cc406Sopenharmony_ci                      genesys::TestScannerInterface& iface,
214141cc406Sopenharmony_ci                      const std::string& checkpoint_name,
215141cc406Sopenharmony_ci                      std::stringstream& out)
216141cc406Sopenharmony_ci{
217141cc406Sopenharmony_ci    out << "\n\n================\n"
218141cc406Sopenharmony_ci        << "Checkpoint: " << checkpoint_name << "\n"
219141cc406Sopenharmony_ci        << "================\n\n"
220141cc406Sopenharmony_ci        << "dev: " << genesys::format_indent_braced_list(4, dev) << "\n\n"
221141cc406Sopenharmony_ci        << "iface.cached_regs: "
222141cc406Sopenharmony_ci        << genesys::format_indent_braced_list(4, iface.cached_regs()) << "\n\n"
223141cc406Sopenharmony_ci        << "iface.cached_fe_regs: "
224141cc406Sopenharmony_ci        << genesys::format_indent_braced_list(4, iface.cached_fe_regs()) << "\n\n"
225141cc406Sopenharmony_ci        << "iface.last_progress_message: " << iface.last_progress_message() << "\n\n";
226141cc406Sopenharmony_ci    out << "iface.slope_tables: {\n";
227141cc406Sopenharmony_ci    for (const auto& kv : iface.recorded_slope_tables()) {
228141cc406Sopenharmony_ci        out << "    " << kv.first << ": {";
229141cc406Sopenharmony_ci        for (unsigned i = 0; i < kv.second.size(); ++i) {
230141cc406Sopenharmony_ci            if (i % 10 == 0) {
231141cc406Sopenharmony_ci                out << "\n       ";
232141cc406Sopenharmony_ci            }
233141cc406Sopenharmony_ci            out << ' ' << kv.second[i];
234141cc406Sopenharmony_ci        }
235141cc406Sopenharmony_ci        out << "\n    }\n";
236141cc406Sopenharmony_ci    }
237141cc406Sopenharmony_ci    out << "}\n";
238141cc406Sopenharmony_ci    if (iface.recorded_key_values().empty()) {
239141cc406Sopenharmony_ci        out << "iface.recorded_key_values: []\n";
240141cc406Sopenharmony_ci    } else {
241141cc406Sopenharmony_ci        out << "iface.recorded_key_values: {\n";
242141cc406Sopenharmony_ci        for (const auto& kv : iface.recorded_key_values()) {
243141cc406Sopenharmony_ci            out << "    " << kv.first << " : " << kv.second << '\n';
244141cc406Sopenharmony_ci        }
245141cc406Sopenharmony_ci        out << "}\n";
246141cc406Sopenharmony_ci    }
247141cc406Sopenharmony_ci    iface.recorded_key_values().clear();
248141cc406Sopenharmony_ci    out << "\n";
249141cc406Sopenharmony_ci}
250141cc406Sopenharmony_ci
251141cc406Sopenharmony_civoid run_single_test_scan(const TestConfig& config, std::stringstream& out)
252141cc406Sopenharmony_ci{
253141cc406Sopenharmony_ci    auto print_checkpoint_wrapper = [&](const genesys::Genesys_Device& dev,
254141cc406Sopenharmony_ci                                        genesys::TestScannerInterface& iface,
255141cc406Sopenharmony_ci                                        const std::string& checkpoint_name)
256141cc406Sopenharmony_ci    {
257141cc406Sopenharmony_ci        print_checkpoint(dev, iface, checkpoint_name, out);
258141cc406Sopenharmony_ci    };
259141cc406Sopenharmony_ci
260141cc406Sopenharmony_ci    genesys::enable_testing_mode(config.vendor_id, config.product_id, config.bcd_device,
261141cc406Sopenharmony_ci                                 print_checkpoint_wrapper);
262141cc406Sopenharmony_ci
263141cc406Sopenharmony_ci    SANE_Handle handle;
264141cc406Sopenharmony_ci
265141cc406Sopenharmony_ci    TIE(sane_init(nullptr, nullptr));
266141cc406Sopenharmony_ci    TIE(sane_open(genesys::get_testing_device_name().c_str(), &handle));
267141cc406Sopenharmony_ci
268141cc406Sopenharmony_ci    SaneOptions options;
269141cc406Sopenharmony_ci    options.fetch(handle);
270141cc406Sopenharmony_ci
271141cc406Sopenharmony_ci    options.set_value_button("force-calibration", true);
272141cc406Sopenharmony_ci    options.set_value_string(SANE_NAME_SCAN_SOURCE,
273141cc406Sopenharmony_ci                             genesys::scan_method_to_option_string(config.method));
274141cc406Sopenharmony_ci    options.set_value_string(SANE_NAME_SCAN_MODE,
275141cc406Sopenharmony_ci                             genesys::scan_color_mode_to_option_string(config.color_mode));
276141cc406Sopenharmony_ci    if (config.color_mode != genesys::ScanColorMode::LINEART) {
277141cc406Sopenharmony_ci        options.set_value_int(SANE_NAME_BIT_DEPTH, config.depth);
278141cc406Sopenharmony_ci    }
279141cc406Sopenharmony_ci    options.set_value_int(SANE_NAME_SCAN_RESOLUTION, config.resolution);
280141cc406Sopenharmony_ci    options.close();
281141cc406Sopenharmony_ci
282141cc406Sopenharmony_ci    TIE(sane_start(handle));
283141cc406Sopenharmony_ci
284141cc406Sopenharmony_ci    SANE_Parameters params;
285141cc406Sopenharmony_ci    TIE(sane_get_parameters(handle, &params));
286141cc406Sopenharmony_ci
287141cc406Sopenharmony_ci    print_params(params, out);
288141cc406Sopenharmony_ci
289141cc406Sopenharmony_ci    int buffer_size = 1024 * 1024;
290141cc406Sopenharmony_ci    std::vector<std::uint8_t> buffer;
291141cc406Sopenharmony_ci    buffer.resize(buffer_size);
292141cc406Sopenharmony_ci
293141cc406Sopenharmony_ci    std::uint64_t total_data_size = std::uint64_t(params.bytes_per_line) * params.lines;
294141cc406Sopenharmony_ci    std::uint64_t total_got_data = 0;
295141cc406Sopenharmony_ci
296141cc406Sopenharmony_ci    while (total_got_data < total_data_size) {
297141cc406Sopenharmony_ci        int ask_len = std::min<std::size_t>(buffer_size, total_data_size - total_got_data);
298141cc406Sopenharmony_ci
299141cc406Sopenharmony_ci        int got_data = 0;
300141cc406Sopenharmony_ci        auto status = sane_read(handle, buffer.data(), ask_len, &got_data);
301141cc406Sopenharmony_ci        total_got_data += got_data;
302141cc406Sopenharmony_ci        if (status == SANE_STATUS_EOF) {
303141cc406Sopenharmony_ci            break;
304141cc406Sopenharmony_ci        }
305141cc406Sopenharmony_ci        TIE(status);
306141cc406Sopenharmony_ci    }
307141cc406Sopenharmony_ci
308141cc406Sopenharmony_ci    sane_cancel(handle);
309141cc406Sopenharmony_ci    sane_close(handle);
310141cc406Sopenharmony_ci    sane_exit();
311141cc406Sopenharmony_ci
312141cc406Sopenharmony_ci    genesys::disable_testing_mode();
313141cc406Sopenharmony_ci}
314141cc406Sopenharmony_ci
315141cc406Sopenharmony_cistd::string read_file_to_string(const std::string& path)
316141cc406Sopenharmony_ci{
317141cc406Sopenharmony_ci    std::ifstream in;
318141cc406Sopenharmony_ci    in.open(path);
319141cc406Sopenharmony_ci    if (!in.is_open()) {
320141cc406Sopenharmony_ci        return "";
321141cc406Sopenharmony_ci    }
322141cc406Sopenharmony_ci    std::stringstream in_str;
323141cc406Sopenharmony_ci    in_str << in.rdbuf();
324141cc406Sopenharmony_ci    return in_str.str();
325141cc406Sopenharmony_ci}
326141cc406Sopenharmony_ci
327141cc406Sopenharmony_civoid write_string_to_file(const std::string& path, const std::string& contents)
328141cc406Sopenharmony_ci{
329141cc406Sopenharmony_ci    std::ofstream out;
330141cc406Sopenharmony_ci    out.open(path);
331141cc406Sopenharmony_ci    if (!out.is_open()) {
332141cc406Sopenharmony_ci        throw std::runtime_error("Could not open output file: " + path);
333141cc406Sopenharmony_ci    }
334141cc406Sopenharmony_ci    out << contents;
335141cc406Sopenharmony_ci    out.close();
336141cc406Sopenharmony_ci}
337141cc406Sopenharmony_ci
338141cc406Sopenharmony_cistruct TestResult
339141cc406Sopenharmony_ci{
340141cc406Sopenharmony_ci    bool success = true;
341141cc406Sopenharmony_ci    TestConfig config;
342141cc406Sopenharmony_ci    std::string failure_message;
343141cc406Sopenharmony_ci};
344141cc406Sopenharmony_ci
345141cc406Sopenharmony_ciTestResult perform_single_test(const TestConfig& config, const std::string& check_directory,
346141cc406Sopenharmony_ci                               const std::string& output_directory)
347141cc406Sopenharmony_ci{
348141cc406Sopenharmony_ci    TestResult test_result;
349141cc406Sopenharmony_ci    test_result.config = config;
350141cc406Sopenharmony_ci
351141cc406Sopenharmony_ci    std::stringstream result_output_stream;
352141cc406Sopenharmony_ci    std::string exception_output;
353141cc406Sopenharmony_ci    try {
354141cc406Sopenharmony_ci        run_single_test_scan(config, result_output_stream);
355141cc406Sopenharmony_ci    } catch (const std::exception& exc) {
356141cc406Sopenharmony_ci        exception_output = std::string("got exception: ") + typeid(exc).name() +
357141cc406Sopenharmony_ci                           " with message\n" + exc.what() + "\n";
358141cc406Sopenharmony_ci        test_result.success = false;
359141cc406Sopenharmony_ci        test_result.failure_message += exception_output;
360141cc406Sopenharmony_ci    } catch (...) {
361141cc406Sopenharmony_ci        exception_output = "got unknown exception\n";
362141cc406Sopenharmony_ci        test_result.success = false;
363141cc406Sopenharmony_ci        test_result.failure_message += exception_output;
364141cc406Sopenharmony_ci    }
365141cc406Sopenharmony_ci    auto result_output = result_output_stream.str();
366141cc406Sopenharmony_ci    if (!exception_output.empty()) {
367141cc406Sopenharmony_ci        result_output += "\n\n" + exception_output;
368141cc406Sopenharmony_ci    }
369141cc406Sopenharmony_ci
370141cc406Sopenharmony_ci    auto test_filename = config.name() + ".txt";
371141cc406Sopenharmony_ci    auto expected_session_path = check_directory + "/" + test_filename;
372141cc406Sopenharmony_ci    auto current_session_path = output_directory + "/" + test_filename;
373141cc406Sopenharmony_ci
374141cc406Sopenharmony_ci    auto expected_output = read_file_to_string(expected_session_path);
375141cc406Sopenharmony_ci
376141cc406Sopenharmony_ci    bool has_output = !output_directory.empty();
377141cc406Sopenharmony_ci
378141cc406Sopenharmony_ci    if (has_output) {
379141cc406Sopenharmony_ci        mkdir(output_directory.c_str(), 0777);
380141cc406Sopenharmony_ci        // note that check_directory and output_directory may be the same, so make sure removal
381141cc406Sopenharmony_ci        // happens after the expected output has already been read.
382141cc406Sopenharmony_ci        std::remove(current_session_path.c_str());
383141cc406Sopenharmony_ci    }
384141cc406Sopenharmony_ci
385141cc406Sopenharmony_ci    if (expected_output.empty()) {
386141cc406Sopenharmony_ci        test_result.failure_message += "the expected data file does not exist\n";
387141cc406Sopenharmony_ci        test_result.success = false;
388141cc406Sopenharmony_ci    } else if (expected_output != result_output) {
389141cc406Sopenharmony_ci        test_result.failure_message += "expected and current output are not equal\n";
390141cc406Sopenharmony_ci        if (has_output) {
391141cc406Sopenharmony_ci            test_result.failure_message += "To examine, run:\ndiff -u \"" + current_session_path +
392141cc406Sopenharmony_ci                                           "\" \"" + expected_session_path + "\"\n";
393141cc406Sopenharmony_ci        }
394141cc406Sopenharmony_ci        test_result.success = false;
395141cc406Sopenharmony_ci    }
396141cc406Sopenharmony_ci
397141cc406Sopenharmony_ci    if (has_output) {
398141cc406Sopenharmony_ci        write_string_to_file(current_session_path, result_output);
399141cc406Sopenharmony_ci    }
400141cc406Sopenharmony_ci    return test_result;
401141cc406Sopenharmony_ci}
402141cc406Sopenharmony_ci
403141cc406Sopenharmony_cistd::vector<TestConfig> get_all_test_configs()
404141cc406Sopenharmony_ci{
405141cc406Sopenharmony_ci    genesys::genesys_init_usb_device_tables();
406141cc406Sopenharmony_ci    genesys::genesys_init_sensor_tables();
407141cc406Sopenharmony_ci    genesys::verify_usb_device_tables();
408141cc406Sopenharmony_ci    genesys::verify_sensor_tables();
409141cc406Sopenharmony_ci
410141cc406Sopenharmony_ci    std::vector<TestConfig> configs;
411141cc406Sopenharmony_ci    std::unordered_set<std::string> model_names;
412141cc406Sopenharmony_ci
413141cc406Sopenharmony_ci    for (const auto& usb_dev : *genesys::s_usb_devices) {
414141cc406Sopenharmony_ci
415141cc406Sopenharmony_ci        const auto& model = usb_dev.model();
416141cc406Sopenharmony_ci
417141cc406Sopenharmony_ci        if (genesys::has_flag(model.flags, genesys::ModelFlag::UNTESTED)) {
418141cc406Sopenharmony_ci            continue;
419141cc406Sopenharmony_ci        }
420141cc406Sopenharmony_ci        if (model_names.find(model.name) != model_names.end()) {
421141cc406Sopenharmony_ci            continue;
422141cc406Sopenharmony_ci        }
423141cc406Sopenharmony_ci        model_names.insert(model.name);
424141cc406Sopenharmony_ci
425141cc406Sopenharmony_ci        for (auto scan_mode : { genesys::ScanColorMode::GRAY,
426141cc406Sopenharmony_ci                                genesys::ScanColorMode::COLOR_SINGLE_PASS }) {
427141cc406Sopenharmony_ci
428141cc406Sopenharmony_ci            auto depth_values = model.bpp_gray_values;
429141cc406Sopenharmony_ci            if (scan_mode == genesys::ScanColorMode::COLOR_SINGLE_PASS) {
430141cc406Sopenharmony_ci                depth_values = model.bpp_color_values;
431141cc406Sopenharmony_ci            }
432141cc406Sopenharmony_ci            for (unsigned depth : depth_values) {
433141cc406Sopenharmony_ci                for (auto method_resolutions : model.resolutions) {
434141cc406Sopenharmony_ci                    for (auto method : method_resolutions.methods) {
435141cc406Sopenharmony_ci                        for (unsigned resolution : method_resolutions.get_resolutions()) {
436141cc406Sopenharmony_ci                            TestConfig config;
437141cc406Sopenharmony_ci                            config.vendor_id = usb_dev.vendor_id();
438141cc406Sopenharmony_ci                            config.product_id = usb_dev.product_id();
439141cc406Sopenharmony_ci                            config.bcd_device = usb_dev.bcd_device();
440141cc406Sopenharmony_ci                            config.model_name = model.name;
441141cc406Sopenharmony_ci                            config.method = method;
442141cc406Sopenharmony_ci                            config.depth = depth;
443141cc406Sopenharmony_ci                            config.resolution = resolution;
444141cc406Sopenharmony_ci                            config.color_mode = scan_mode;
445141cc406Sopenharmony_ci                            configs.push_back(config);
446141cc406Sopenharmony_ci                        }
447141cc406Sopenharmony_ci                    }
448141cc406Sopenharmony_ci                }
449141cc406Sopenharmony_ci            }
450141cc406Sopenharmony_ci        }
451141cc406Sopenharmony_ci    }
452141cc406Sopenharmony_ci    return configs;
453141cc406Sopenharmony_ci}
454141cc406Sopenharmony_ci
455141cc406Sopenharmony_civoid print_help()
456141cc406Sopenharmony_ci{
457141cc406Sopenharmony_ci    std::cerr << "Usage:\n"
458141cc406Sopenharmony_ci              << "session_config_test [--test={test_name}] {check_directory} [{output_directory}]\n"
459141cc406Sopenharmony_ci              << "session_config_test --help\n"
460141cc406Sopenharmony_ci              << "session_config_test --print_test_names\n";
461141cc406Sopenharmony_ci}
462141cc406Sopenharmony_ci
463141cc406Sopenharmony_ciint main(int argc, const char* argv[])
464141cc406Sopenharmony_ci{
465141cc406Sopenharmony_ci    std::string check_directory;
466141cc406Sopenharmony_ci    std::string output_directory;
467141cc406Sopenharmony_ci    std::string test_name_filter;
468141cc406Sopenharmony_ci    bool print_test_names = false;
469141cc406Sopenharmony_ci
470141cc406Sopenharmony_ci    for (int argi = 1; argi < argc; ++argi) {
471141cc406Sopenharmony_ci        std::string arg = argv[argi];
472141cc406Sopenharmony_ci        if (arg.rfind("--test=", 0) == 0) {
473141cc406Sopenharmony_ci            test_name_filter = arg.substr(7);
474141cc406Sopenharmony_ci        } else if (arg == "-h" || arg == "--help") {
475141cc406Sopenharmony_ci            print_help();
476141cc406Sopenharmony_ci            return 0;
477141cc406Sopenharmony_ci        } else if (arg == "--print_test_names") {
478141cc406Sopenharmony_ci            print_test_names = true;
479141cc406Sopenharmony_ci        } else if (check_directory.empty()) {
480141cc406Sopenharmony_ci            check_directory = arg;
481141cc406Sopenharmony_ci        } else if (output_directory.empty()) {
482141cc406Sopenharmony_ci            output_directory = arg;
483141cc406Sopenharmony_ci        }
484141cc406Sopenharmony_ci    }
485141cc406Sopenharmony_ci
486141cc406Sopenharmony_ci    auto configs = get_all_test_configs();
487141cc406Sopenharmony_ci
488141cc406Sopenharmony_ci    if (print_test_names) {
489141cc406Sopenharmony_ci        for (const auto& config : configs) {
490141cc406Sopenharmony_ci            std::cout << config.name() << "\n";
491141cc406Sopenharmony_ci        }
492141cc406Sopenharmony_ci        return 0;
493141cc406Sopenharmony_ci    }
494141cc406Sopenharmony_ci
495141cc406Sopenharmony_ci    if (check_directory.empty()) {
496141cc406Sopenharmony_ci        print_help();
497141cc406Sopenharmony_ci        return 1;
498141cc406Sopenharmony_ci    }
499141cc406Sopenharmony_ci
500141cc406Sopenharmony_ci    bool test_success = true;
501141cc406Sopenharmony_ci    for (unsigned i = 0; i < configs.size(); ++i) {
502141cc406Sopenharmony_ci        const auto& config = configs[i];
503141cc406Sopenharmony_ci
504141cc406Sopenharmony_ci        if (!test_name_filter.empty() && config.name() != test_name_filter) {
505141cc406Sopenharmony_ci            continue;
506141cc406Sopenharmony_ci        }
507141cc406Sopenharmony_ci
508141cc406Sopenharmony_ci        auto result = perform_single_test(config, check_directory, output_directory);
509141cc406Sopenharmony_ci        std::cerr << "(" << i << "/" << configs.size() << "): "
510141cc406Sopenharmony_ci                  << (result.success ? "SUCCESS: " : "FAIL: ")
511141cc406Sopenharmony_ci                  << result.config.name() << "\n";
512141cc406Sopenharmony_ci        if (!result.success) {
513141cc406Sopenharmony_ci            std::cerr << result.failure_message;
514141cc406Sopenharmony_ci        }
515141cc406Sopenharmony_ci
516141cc406Sopenharmony_ci        test_success &= result.success;
517141cc406Sopenharmony_ci    }
518141cc406Sopenharmony_ci
519141cc406Sopenharmony_ci    if (!test_success) {
520141cc406Sopenharmony_ci        return 1;
521141cc406Sopenharmony_ci    }
522141cc406Sopenharmony_ci    return 0;
523141cc406Sopenharmony_ci}
524