| // Copyright (c) 2013 The Chromium OS Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "lorgnette/manager.h" | 
 |  | 
 | #include <stdint.h> | 
 |  | 
 | #include <memory> | 
 | #include <string> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include <base/files/file.h> | 
 | #include <base/files/file_path.h> | 
 | #include <base/files/file_util.h> | 
 | #include <base/files/scoped_file.h> | 
 | #include <base/files/scoped_temp_dir.h> | 
 | #include <base/time/time.h> | 
 | #include <brillo/dbus/mock_dbus_method_response.h> | 
 | #include <brillo/process/process.h> | 
 | #include <chromeos/dbus/service_constants.h> | 
 | #include <gmock/gmock.h> | 
 | #include <gtest/gtest.h> | 
 | #include <lorgnette/proto_bindings/lorgnette_service.pb.h> | 
 | #include <metrics/metrics_library_mock.h> | 
 | #include <sane/sane.h> | 
 |  | 
 | #include "lorgnette/enums.h" | 
 | #include "lorgnette/sane_client_fake.h" | 
 | #include "lorgnette/test_util.h" | 
 |  | 
 | using brillo::dbus_utils::MockDBusMethodResponse; | 
 | using ::testing::ContainsRegex; | 
 | using ::testing::ElementsAre; | 
 |  | 
 | namespace lorgnette { | 
 |  | 
 | namespace { | 
 |  | 
 | void ValidateProgressSignals( | 
 |     const std::vector<ScanStatusChangedSignal>& signals, | 
 |     const std::string& scan_uuid) { | 
 |   int progress = 0; | 
 |   int page = 1; | 
 |   for (int i = 0; i < signals.size() - 1; i++) { | 
 |     const ScanStatusChangedSignal& signal = signals[i]; | 
 |     EXPECT_EQ(signal.scan_uuid(), scan_uuid); | 
 |     EXPECT_EQ(signal.page(), page); | 
 |  | 
 |     if (signal.state() == SCAN_STATE_IN_PROGRESS) { | 
 |       EXPECT_GT(signal.progress(), progress); | 
 |       progress = signal.progress(); | 
 |     } else if (signal.state() == SCAN_STATE_PAGE_COMPLETED) { | 
 |       page++; | 
 |       progress = 0; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void ValidateSignals(const std::vector<ScanStatusChangedSignal>& signals, | 
 |                      const std::string& scan_uuid) { | 
 |   EXPECT_GE(signals.size(), 1); | 
 |   EXPECT_EQ(signals.back().scan_uuid(), scan_uuid); | 
 |   EXPECT_EQ(signals.back().state(), SCAN_STATE_COMPLETED); | 
 |  | 
 |   ValidateProgressSignals(signals, scan_uuid); | 
 | } | 
 |  | 
 | template <typename T> | 
 | std::unique_ptr<MockDBusMethodResponse<std::vector<uint8_t>>> | 
 | BuildMockDBusResponse(T* response) { | 
 |   auto dbus_response = | 
 |       std::make_unique<MockDBusMethodResponse<std::vector<uint8_t>>>(); | 
 |   dbus_response->set_return_callback(base::BindRepeating( | 
 |       [](T* response_out, const std::vector<uint8_t>& serialized_response) { | 
 |         ASSERT_TRUE(response_out); | 
 |         ASSERT_TRUE(response_out->ParseFromArray(serialized_response.data(), | 
 |                                                  serialized_response.size())); | 
 |       }, | 
 |       base::Unretained(response))); | 
 |   return dbus_response; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | class ManagerTest : public testing::Test { | 
 |  protected: | 
 |   ManagerTest() | 
 |       : sane_client_(new SaneClientFake()), | 
 |         manager_(base::Callback<void()>(), | 
 |                  std::unique_ptr<SaneClient>(sane_client_)), | 
 |         metrics_library_(new MetricsLibraryMock) { | 
 |     manager_.metrics_library_.reset(metrics_library_); | 
 |     manager_.SetProgressSignalInterval(base::TimeDelta::FromSeconds(0)); | 
 |   } | 
 |  | 
 |   void SetUp() override { | 
 |     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); | 
 |     output_path_ = temp_dir_.GetPath().Append("scan_data.png"); | 
 |     base::File scan(output_path_, | 
 |                     base::File::FLAG_CREATE | base::File::FLAG_WRITE); | 
 |     ASSERT_TRUE(scan.IsValid()); | 
 |     scan_fd_ = base::ScopedFD(scan.TakePlatformFile()); | 
 |  | 
 |     manager_.SetScanStatusChangedSignalSenderForTest(base::BindRepeating( | 
 |         [](std::vector<ScanStatusChangedSignal>* signals, | 
 |            const ScanStatusChangedSignal& signal) { | 
 |           signals->push_back(signal); | 
 |         }, | 
 |         base::Unretained(&signals_))); | 
 |   } | 
 |  | 
 |   void ExpectScanRequest(DocumentScanSaneBackend backend) { | 
 |     EXPECT_CALL(*metrics_library_, | 
 |                 SendEnumToUMA(Manager::kMetricScanRequested, backend, | 
 |                               DocumentScanSaneBackend::kMaxValue)); | 
 |   } | 
 |  | 
 |   void ExpectScanSuccess(DocumentScanSaneBackend backend) { | 
 |     EXPECT_CALL(*metrics_library_, | 
 |                 SendEnumToUMA(Manager::kMetricScanSucceeded, backend, | 
 |                               DocumentScanSaneBackend::kMaxValue)); | 
 |   } | 
 |  | 
 |   void ExpectScanFailure(DocumentScanSaneBackend backend) { | 
 |     EXPECT_CALL(*metrics_library_, | 
 |                 SendEnumToUMA(Manager::kMetricScanFailed, backend, | 
 |                               DocumentScanSaneBackend::kMaxValue)); | 
 |   } | 
 |  | 
 |   void CompareImages(const std::string& path_a, const std::string& path_b) { | 
 |     brillo::ProcessImpl diff; | 
 |     diff.AddArg("/usr/bin/perceptualdiff"); | 
 |     diff.AddArg("-verbose"); | 
 |     diff.AddIntOption("-threshold", 1); | 
 |     diff.AddArg(path_a); | 
 |     diff.AddArg(path_b); | 
 |     EXPECT_EQ(diff.Run(), 0) | 
 |         << path_a << " and " << path_b << " are not the same image"; | 
 |   } | 
 |  | 
 |   void SetUpTestDevice(const std::string& name, | 
 |                        const std::vector<base::FilePath>& image_paths, | 
 |                        const ScanParameters& parameters) { | 
 |     std::vector<std::vector<uint8_t>> pages; | 
 |     for (const base::FilePath& path : image_paths) { | 
 |       std::string contents; | 
 |       ASSERT_TRUE(base::ReadFileToString(path, &contents)); | 
 |       std::vector<uint8_t> image_data(contents.begin(), contents.end()); | 
 |       pages.push_back(image_data); | 
 |     } | 
 |  | 
 |     std::unique_ptr<SaneDeviceFake> device = std::make_unique<SaneDeviceFake>(); | 
 |     device->SetScanData(pages); | 
 |     device->SetScanParameters(parameters); | 
 |     sane_client_->SetDeviceForName(name, std::move(device)); | 
 |   } | 
 |  | 
 |   // Set up a multi-page color scan. | 
 |   void SetUpMultiPageScan() { | 
 |     ScanParameters parameters; | 
 |     parameters.format = kRGB; | 
 |     parameters.bytes_per_line = 98 * 3; | 
 |     parameters.pixels_per_line = 98; | 
 |     parameters.lines = 50; | 
 |     parameters.depth = 8; | 
 |     base::FilePath path("test_images/color.pnm"); | 
 |     SetUpTestDevice("TestDevice", {path, path}, parameters); | 
 |   } | 
 |  | 
 |   StartScanResponse StartScan(const std::string& device_name, | 
 |                               ColorMode color_mode, | 
 |                               const std::string& source_name) { | 
 |     StartScanRequest request; | 
 |     request.set_device_name(device_name); | 
 |     request.mutable_settings()->set_color_mode(color_mode); | 
 |     request.mutable_settings()->set_source_name(source_name); | 
 |  | 
 |     std::vector<uint8_t> serialized_response = | 
 |         manager_.StartScan(impl::SerializeProto(request)); | 
 |  | 
 |     StartScanResponse response; | 
 |     EXPECT_TRUE(response.ParseFromArray(serialized_response.data(), | 
 |                                         serialized_response.size())); | 
 |     return response; | 
 |   } | 
 |  | 
 |   GetNextImageResponse GetNextImage(const std::string& scan_uuid, | 
 |                                     const base::ScopedFD& output_fd) { | 
 |     GetNextImageRequest request; | 
 |     request.set_scan_uuid(scan_uuid); | 
 |  | 
 |     GetNextImageResponse response; | 
 |     manager_.GetNextImage(BuildMockDBusResponse(&response), | 
 |                           impl::SerializeProto(request), output_fd); | 
 |     return response; | 
 |   } | 
 |  | 
 |   CancelScanResponse CancelScan(const std::string& scan_uuid) { | 
 |     CancelScanRequest request; | 
 |     request.set_scan_uuid(scan_uuid); | 
 |  | 
 |     std::vector<uint8_t> serialized_response = | 
 |         manager_.CancelScan(impl::SerializeProto(request)); | 
 |  | 
 |     CancelScanResponse response; | 
 |     EXPECT_TRUE(response.ParseFromArray(serialized_response.data(), | 
 |                                         serialized_response.size())); | 
 |     return response; | 
 |   } | 
 |  | 
 |   // Run a one-page scan to completion, and verify that it was successful. | 
 |   void RunScanSuccess(const std::string& device_name, ColorMode color_mode) { | 
 |     StartScanResponse response = StartScan(device_name, color_mode, "Flatbed"); | 
 |     EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |     EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |     GetNextImageResponse get_next_image_response = | 
 |         GetNextImage(response.scan_uuid(), scan_fd_); | 
 |     EXPECT_TRUE(get_next_image_response.success()); | 
 |  | 
 |     ValidateSignals(signals_, response.scan_uuid()); | 
 |   } | 
 |  | 
 |   std::vector<ScanStatusChangedSignal> signals_; | 
 |  | 
 |   SaneClientFake* sane_client_; | 
 |   Manager manager_; | 
 |   MetricsLibraryMock* metrics_library_;  // Owned by manager_. | 
 |   base::ScopedTempDir temp_dir_; | 
 |   base::FilePath output_path_; | 
 |   base::ScopedFD scan_fd_; | 
 | }; | 
 |  | 
 | TEST_F(ManagerTest, GetScannerCapabilitiesInvalidIppUsbFailure) { | 
 |   std::vector<uint8_t> serialized; | 
 |   brillo::ErrorPtr error; | 
 |   EXPECT_FALSE( | 
 |       manager_.GetScannerCapabilities(&error, "ippusb:invalid", &serialized)); | 
 |   EXPECT_NE(error, nullptr); | 
 |   EXPECT_NE(error->GetMessage().find("ippusb"), std::string::npos); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, GetScannerCapabilitiesSuccess) { | 
 |   std::unique_ptr<SaneDeviceFake> device = std::make_unique<SaneDeviceFake>(); | 
 |   ValidOptionValues opts; | 
 |   opts.resolutions = {50, 100, 200, 300, 500, 600}; | 
 |   opts.sources = { | 
 |       CreateDocumentSource("FB", SOURCE_PLATEN, 355.2, 417.9), | 
 |       CreateDocumentSource("Negative", SOURCE_UNSPECIFIED, 355.2, 204.0), | 
 |       CreateDocumentSource("Automatic Document Feeder", SOURCE_ADF_SIMPLEX, | 
 |                            212.9, 212.2)}; | 
 |   opts.color_modes = {kScanPropertyModeColor}; | 
 |   device->SetValidOptionValues(opts); | 
 |   sane_client_->SetDeviceForName("TestDevice", std::move(device)); | 
 |  | 
 |   std::vector<uint8_t> serialized; | 
 |   EXPECT_TRUE( | 
 |       manager_.GetScannerCapabilities(nullptr, "TestDevice", &serialized)); | 
 |  | 
 |   ScannerCapabilities caps; | 
 |   EXPECT_TRUE(caps.ParseFromArray(serialized.data(), serialized.size())); | 
 |  | 
 |   EXPECT_THAT(caps.resolutions(), ElementsAre(100, 200, 300, 600)); | 
 |  | 
 |   EXPECT_THAT(caps.sources(), | 
 |               ElementsAre(EqualsDocumentSource(CreateDocumentSource( | 
 |                               "FB", SOURCE_PLATEN, 355.2, 417.9)), | 
 |                           EqualsDocumentSource(CreateDocumentSource( | 
 |                               "Automatic Document Feeder", SOURCE_ADF_SIMPLEX, | 
 |                               212.9, 212.2)))); | 
 |  | 
 |   EXPECT_THAT(caps.color_modes(), ElementsAre(MODE_COLOR)); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanBlackAndWhiteSuccess) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kGrayscale; | 
 |   parameters.bytes_per_line = 11; | 
 |   parameters.pixels_per_line = 85; | 
 |   parameters.lines = 29; | 
 |   parameters.depth = 1; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/bw.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanSuccess(kOtherBackend); | 
 |   RunScanSuccess("TestDevice", MODE_LINEART); | 
 |   CompareImages("./test_images/bw.png", output_path_.value()); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanGrayscaleSuccess) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kGrayscale; | 
 |   parameters.pixels_per_line = 32; | 
 |   parameters.lines = 32; | 
 |   parameters.depth = 8; | 
 |   parameters.bytes_per_line = parameters.pixels_per_line * parameters.depth / 8; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/gray.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanSuccess(kOtherBackend); | 
 |   RunScanSuccess("TestDevice", MODE_GRAYSCALE); | 
 |   CompareImages("./test_images/gray.png", output_path_.value()); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanColorSuccess) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kRGB; | 
 |   parameters.bytes_per_line = 98 * 3; | 
 |   parameters.pixels_per_line = 98; | 
 |   parameters.lines = 50; | 
 |   parameters.depth = 8; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/color.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanSuccess(kOtherBackend); | 
 |   RunScanSuccess("TestDevice", MODE_COLOR); | 
 |   CompareImages("./test_images/color.png", output_path_.value()); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScan16BitColorSuccess) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kRGB; | 
 |   parameters.pixels_per_line = 32; | 
 |   parameters.lines = 32; | 
 |   parameters.depth = 16; | 
 |   parameters.bytes_per_line = | 
 |       parameters.pixels_per_line * parameters.depth / 8 * 3; | 
 |   // Note: technically, color16.pnm does not really contain PNM data, since | 
 |   // NetPBM assumes big endian 16-bit samples. Since SANE provides | 
 |   // endian-native samples, color16.pnm stores the samples as little-endian. | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/color16.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanSuccess(kOtherBackend); | 
 |   RunScanSuccess("TestDevice", MODE_COLOR); | 
 |   CompareImages("./test_images/color16.png", output_path_.value()); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanMultiPageColorSuccess) { | 
 |   SetUpMultiPageScan(); | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanSuccess(kOtherBackend); | 
 |  | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "ADF"); | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |   CompareImages("./test_images/color.png", output_path_.value()); | 
 |  | 
 |   base::FilePath second_page = temp_dir_.GetPath().Append("scan_data2.png"); | 
 |   base::File scan(second_page, | 
 |                   base::File::FLAG_CREATE | base::File::FLAG_WRITE); | 
 |   ASSERT_TRUE(scan.IsValid()); | 
 |   base::ScopedFD second_page_fd(scan.TakePlatformFile()); | 
 |  | 
 |   get_next_image_response = GetNextImage(response.scan_uuid(), second_page_fd); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |   CompareImages("./test_images/color.png", second_page.value()); | 
 |  | 
 |   ValidateSignals(signals_, response.scan_uuid()); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanCancelledImmediately) { | 
 |   SetUpMultiPageScan(); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   // Set the source to "ADF" so that lorgnette knows to expect multiple pages. | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "ADF"); | 
 |   std::string uuid = response.scan_uuid(); | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_NE(uuid, ""); | 
 |  | 
 |   CancelScanResponse cancel_scan_response = CancelScan(uuid); | 
 |   EXPECT_TRUE(cancel_scan_response.success()); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = GetNextImage(uuid, scan_fd_); | 
 |   EXPECT_FALSE(get_next_image_response.success()); | 
 |  | 
 |   EXPECT_EQ(signals_.back().scan_uuid(), uuid); | 
 |   EXPECT_EQ(signals_.back().state(), SCAN_STATE_CANCELLED); | 
 |   ValidateProgressSignals(signals_, uuid); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanCancelledWithNoFurtherOperations) { | 
 |   SetUpMultiPageScan(); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   // Set the source to "ADF" so that lorgnette knows to expect multiple pages. | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "ADF"); | 
 |   std::string uuid = response.scan_uuid(); | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_NE(uuid, ""); | 
 |  | 
 |   CancelScanResponse cancel_scan_response = CancelScan(uuid); | 
 |   EXPECT_TRUE(cancel_scan_response.success()); | 
 |  | 
 |   EXPECT_EQ(signals_.back().scan_uuid(), uuid); | 
 |   EXPECT_EQ(signals_.back().state(), SCAN_STATE_CANCELLED); | 
 |   ValidateProgressSignals(signals_, uuid); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanCancelledAfterGettingPage) { | 
 |   SetUpMultiPageScan(); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   // Set the source to "ADF" so that lorgnette knows to expect multiple pages. | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "ADF"); | 
 |   std::string uuid = response.scan_uuid(); | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_NE(uuid, ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = GetNextImage(uuid, scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |  | 
 |   CancelScanResponse cancel_scan_response = CancelScan(uuid); | 
 |   EXPECT_TRUE(cancel_scan_response.success()); | 
 |  | 
 |   get_next_image_response = GetNextImage(uuid, scan_fd_); | 
 |   EXPECT_FALSE(get_next_image_response.success()); | 
 |  | 
 |   EXPECT_EQ(signals_.back().scan_uuid(), uuid); | 
 |   EXPECT_EQ(signals_.back().state(), SCAN_STATE_CANCELLED); | 
 |   ValidateProgressSignals(signals_, uuid); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanFailNoDevice) { | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_FAILED); | 
 |   EXPECT_NE(response.failure_reason(), ""); | 
 |   EXPECT_EQ(signals_.size(), 0); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanFailToStart) { | 
 |   std::string contents; | 
 |   ASSERT_TRUE(base::ReadFileToString(base::FilePath("./test_images/color.pnm"), | 
 |                                      &contents)); | 
 |   std::vector<uint8_t> image_data(contents.begin(), contents.end()); | 
 |   std::unique_ptr<SaneDeviceFake> device = std::make_unique<SaneDeviceFake>(); | 
 |   device->SetScanData({image_data}); | 
 |   device->SetStartScanResult(SANE_STATUS_IO_ERROR); | 
 |   sane_client_->SetDeviceForName("TestDevice", std::move(device)); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanFailure(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_FAILED); | 
 |   EXPECT_NE(response.failure_reason(), ""); | 
 |   EXPECT_EQ(signals_.size(), 0); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, StartScanFailToRead) { | 
 |   std::string contents; | 
 |   ASSERT_TRUE(base::ReadFileToString(base::FilePath("./test_images/color.pnm"), | 
 |                                      &contents)); | 
 |   std::vector<uint8_t> image_data(contents.begin(), contents.end()); | 
 |   std::unique_ptr<SaneDeviceFake> device = std::make_unique<SaneDeviceFake>(); | 
 |   device->SetScanData({image_data}); | 
 |   device->SetReadScanDataResult(SANE_STATUS_IO_ERROR); | 
 |   sane_client_->SetDeviceForName("TestDevice", std::move(device)); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanFailure(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |  | 
 |   EXPECT_EQ(signals_.size(), 1); | 
 |   EXPECT_EQ(signals_[0].scan_uuid(), response.scan_uuid()); | 
 |   EXPECT_EQ(signals_[0].state(), SCAN_STATE_FAILED); | 
 |   EXPECT_NE(signals_[0].failure_reason(), ""); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, GetNextImageBadFd) { | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/color.pnm")}, | 
 |                   ScanParameters()); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), base::ScopedFD()); | 
 |   EXPECT_FALSE(get_next_image_response.success()); | 
 |   EXPECT_NE(get_next_image_response.failure_reason(), ""); | 
 |  | 
 |   // Scan should not have failed. | 
 |   EXPECT_EQ(signals_.size(), 0); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, GetNextImageScanAlreadyComplete) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kGrayscale; | 
 |   parameters.pixels_per_line = 32; | 
 |   parameters.lines = 32; | 
 |   parameters.depth = 8; | 
 |   parameters.bytes_per_line = parameters.pixels_per_line * parameters.depth / 8; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/gray.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanSuccess(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "ADF"); | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |   CompareImages("./test_images/gray.png", output_path_.value()); | 
 |  | 
 |   get_next_image_response = GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_FALSE(get_next_image_response.success()); | 
 |  | 
 |   ValidateSignals(signals_, response.scan_uuid()); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, GetNextImageNegativeWidth) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kRGB; | 
 |   parameters.bytes_per_line = 100; | 
 |   parameters.pixels_per_line = -1; | 
 |   parameters.lines = 11; | 
 |   parameters.depth = 16; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/color.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanFailure(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_EQ(response.failure_reason(), ""); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |   EXPECT_EQ(get_next_image_response.failure_reason(), ""); | 
 |  | 
 |   EXPECT_EQ(signals_.size(), 1); | 
 |   EXPECT_EQ(signals_[0].scan_uuid(), response.scan_uuid()); | 
 |   EXPECT_EQ(signals_[0].state(), SCAN_STATE_FAILED); | 
 |   EXPECT_THAT(signals_[0].failure_reason(), ContainsRegex("invalid width")); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, GetNextImageExcessWidth) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kRGB; | 
 |   parameters.bytes_per_line = 3000003; | 
 |   parameters.pixels_per_line = 1000001; | 
 |   parameters.lines = 100; | 
 |   parameters.depth = 8; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/color.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanFailure(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_EQ(response.failure_reason(), ""); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |   EXPECT_EQ(get_next_image_response.failure_reason(), ""); | 
 |  | 
 |   EXPECT_EQ(signals_.size(), 1); | 
 |   EXPECT_EQ(signals_[0].scan_uuid(), response.scan_uuid()); | 
 |   EXPECT_EQ(signals_[0].state(), SCAN_STATE_FAILED); | 
 |   EXPECT_THAT(signals_[0].failure_reason(), ContainsRegex("invalid width")); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, GetNextImageInvalidHeight) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kRGB; | 
 |   parameters.bytes_per_line = 0x40000000 + (0x10 * 0x08); | 
 |   parameters.pixels_per_line = 0x10; | 
 |   parameters.lines = 0x02000000; | 
 |   parameters.depth = 8; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/color.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanFailure(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_EQ(response.failure_reason(), ""); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |   EXPECT_EQ(get_next_image_response.failure_reason(), ""); | 
 |  | 
 |   EXPECT_EQ(signals_.size(), 1); | 
 |   EXPECT_EQ(signals_[0].scan_uuid(), response.scan_uuid()); | 
 |   EXPECT_EQ(signals_[0].state(), SCAN_STATE_FAILED); | 
 |   EXPECT_THAT(signals_[0].failure_reason(), ContainsRegex("invalid height")); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, GetNextImageMismatchedSizes) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kRGB; | 
 |   parameters.bytes_per_line = 8.5 * 1200; | 
 |   parameters.pixels_per_line = 8.5 * 1200; | 
 |   parameters.lines = 11 * 1200; | 
 |   parameters.depth = 8; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/color.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanFailure(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_EQ(response.failure_reason(), ""); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |   EXPECT_EQ(get_next_image_response.failure_reason(), ""); | 
 |  | 
 |   EXPECT_EQ(signals_.size(), 1); | 
 |   EXPECT_EQ(signals_[0].scan_uuid(), response.scan_uuid()); | 
 |   EXPECT_EQ(signals_[0].state(), SCAN_STATE_FAILED); | 
 |   EXPECT_THAT(signals_[0].failure_reason(), | 
 |               ContainsRegex("bytes_per_line.*too small")); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, GetNextImageTooLarge) { | 
 |   ScanParameters parameters; | 
 |   parameters.format = kRGB; | 
 |   parameters.bytes_per_line = 8.5 * 1200 * 6; | 
 |   parameters.pixels_per_line = 8.5 * 1200; | 
 |   parameters.lines = 11 * 1200; | 
 |   parameters.depth = 16; | 
 |   SetUpTestDevice("TestDevice", {base::FilePath("./test_images/color.pnm")}, | 
 |                   parameters); | 
 |  | 
 |   ExpectScanRequest(kOtherBackend); | 
 |   ExpectScanFailure(kOtherBackend); | 
 |   StartScanResponse response = StartScan("TestDevice", MODE_COLOR, "Flatbed"); | 
 |  | 
 |   EXPECT_EQ(response.state(), SCAN_STATE_IN_PROGRESS); | 
 |   EXPECT_EQ(response.failure_reason(), ""); | 
 |   EXPECT_NE(response.scan_uuid(), ""); | 
 |  | 
 |   GetNextImageResponse get_next_image_response = | 
 |       GetNextImage(response.scan_uuid(), scan_fd_); | 
 |   EXPECT_TRUE(get_next_image_response.success()); | 
 |   EXPECT_EQ(get_next_image_response.failure_reason(), ""); | 
 |  | 
 |   EXPECT_EQ(signals_.size(), 1); | 
 |   EXPECT_EQ(signals_[0].scan_uuid(), response.scan_uuid()); | 
 |   EXPECT_EQ(signals_[0].state(), SCAN_STATE_FAILED); | 
 |   EXPECT_THAT(signals_[0].failure_reason(), | 
 |               ContainsRegex("scan buffer.*too large")); | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, RemoveDupNoRepeats) { | 
 |   std::vector<ScannerInfo> scanners_empty, scanners_present, sane_scanners, | 
 |       expected_present; | 
 |   base::flat_set<std::string> seen_vidpid, seen_busdev; | 
 |  | 
 |   ScannerInfo pixma, epson, fujitsu; | 
 |   pixma.set_name("pixma:1a492785_265798"); | 
 |   epson.set_name("epson2:libusb:004:007"); | 
 |   fujitsu.set_name("fujitsu:ScanSnap iX500:1603948"); | 
 |   sane_scanners.push_back(pixma); | 
 |   sane_scanners.push_back(epson); | 
 |   sane_scanners.push_back(fujitsu); | 
 |   // first make sure it doesn't crash with no seen scanners | 
 |   manager_.RemoveDuplicateScanners(&scanners_empty, seen_vidpid, seen_busdev, | 
 |                                    sane_scanners); | 
 |   EXPECT_EQ(scanners_empty.size(), sane_scanners.size()); | 
 |   for (int s = 0; s < scanners_empty.size(); s++) { | 
 |     EXPECT_EQ(scanners_empty[s].name(), sane_scanners[s].name()); | 
 |   } | 
 |   // now make sure it works with seen scanners and no match | 
 |   ScannerInfo ippusb1, ippusb2; | 
 |   ippusb1.set_name("ippusb:escl:EPSON XP-7100 Series:05a8_1134/eSCL/"); | 
 |   ippusb2.set_name("ippusb:escl:Brother HL-L2539DW series:05d9_0023/eSCL/"); | 
 |   scanners_present.push_back(ippusb1); | 
 |   expected_present.push_back(ippusb1); | 
 |   scanners_present.push_back(ippusb2); | 
 |   expected_present.push_back(ippusb2); | 
 |   seen_vidpid.insert("05a8:1134"); | 
 |   seen_vidpid.insert("05d9:0023"); | 
 |   seen_busdev.insert("006:006"); | 
 |   seen_busdev.insert("001:003"); | 
 |   manager_.RemoveDuplicateScanners(&scanners_present, seen_vidpid, seen_busdev, | 
 |                                    sane_scanners); | 
 |   expected_present.push_back(pixma); | 
 |   expected_present.push_back(epson); | 
 |   expected_present.push_back(fujitsu); | 
 |   EXPECT_EQ(scanners_present.size(), expected_present.size()); | 
 |   for (int s = 0; s < scanners_present.size(); s++) { | 
 |     EXPECT_EQ(scanners_present[s].name(), expected_present[s].name()); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(ManagerTest, RemoveDupWithRepeats) { | 
 |   std::vector<ScannerInfo> scanners_present, sane_scanners, expected_present; | 
 |   base::flat_set<std::string> seen_vidpid, seen_busdev; | 
 |   ScannerInfo ipp_pixma, sane_pixma, ipp_epson, sane_epson, sane_fujitsu; | 
 |  | 
 |   ipp_pixma.set_name("ippusb:escl:Canon TR8500 series:05d9_0023/eSCL/"); | 
 |   seen_vidpid.insert("05d9:0023"); | 
 |   seen_busdev.insert("001:005"); | 
 |   scanners_present.push_back(ipp_pixma); | 
 |   expected_present.push_back(ipp_pixma); | 
 |   ipp_epson.set_name("ippusb:escl:EPSON XP-7100 Series:05a8_1134/eSCL/"); | 
 |   seen_vidpid.insert("05a8:1134"); | 
 |   seen_busdev.insert("004:007"); | 
 |   scanners_present.push_back(ipp_epson); | 
 |   expected_present.push_back(ipp_epson); | 
 |  | 
 |   sane_pixma.set_name("pixma:05d90023_265798"); | 
 |   sane_epson.set_name("epson2:libusb:004:007"); | 
 |   sane_fujitsu.set_name("fujitsu:ScanSnap iX500:1603948"); | 
 |   sane_scanners.push_back(sane_pixma); | 
 |   sane_scanners.push_back(sane_epson); | 
 |   sane_scanners.push_back(sane_fujitsu); | 
 |   manager_.RemoveDuplicateScanners(&scanners_present, seen_vidpid, seen_busdev, | 
 |                                    sane_scanners); | 
 |   expected_present.push_back(sane_fujitsu); | 
 |  | 
 |   EXPECT_EQ(scanners_present.size(), expected_present.size()); | 
 |   for (int s = 0; s < scanners_present.size(); s++) { | 
 |     EXPECT_EQ(scanners_present[s].name(), expected_present[s].name()); | 
 |   } | 
 | } | 
 |  | 
 | TEST(BackendFromDeviceName, IppUsbAndAirscan) { | 
 |   std::vector<std::pair<std::string, DocumentScanSaneBackend>> cases = { | 
 |       {"airscan:escl:HP LaserJet 4:http://192.168.0.15:80/eSCL/", kAirscanHp}, | 
 |       {"airscan:escl:Hewlett-Packard Scanjet Pro 2000:http://localhost/eSCL/", | 
 |        kAirscanHp}, | 
 |       {"airscan:escl:HewlettPackard Scanjet Pro 2000:http://localhost/eSCL/", | 
 |        kAirscanHp}, | 
 |       {"airscan:wsd:Konica Minolta Bizhub 3622:http://192.168.0.15:443/eSCL/", | 
 |        kAirscanKonicaMinolta}, | 
 |       {"airscan:escl:RicohPrinter:http://192.168.0.15:80/eSCL/", kAirscanOther}, | 
 |       {"airscan", kAirscanOther}, | 
 |       {"ippusb:escl:EPSON XP-7100 Series:05a8_1134/eSCL/", kIppUsbEpson}, | 
 |       {"ippusb:escl:Hewlett Packard Scanjet N6310:05a8_1134/eSCL/", kIppUsbHp}, | 
 |       {"ippusb:escl:Lexmark Lexmark MB2236adwe:05a8_1134/eSCL/", | 
 |        kIppUsbLexmark}, | 
 |       {"ippusb:escl:Scanner Kodak i3250:05a8_1134/eSCL/", kIppUsbKodak}, | 
 |       {"ippusb:escl:Ye Olde Unbranded Scanner:05a8_1134/eSCL/", kIppUsbOther}, | 
 |       {"ippusb", kIppUsbOther}, | 
 |   }; | 
 |  | 
 |   for (const auto& [device_name, expected_backend] : cases) { | 
 |     EXPECT_EQ(BackendFromDeviceName(device_name), expected_backend) | 
 |         << "Expected backend for device " << device_name << " was not correct."; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace lorgnette |