chromium/components/exo/data_source_unittest.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/exo/data_source.h"

#include <atomic>

#include "base/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/exo/data_source_delegate.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/test_data_source_delegate.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace exo {
namespace {

using test::TestDataSourceDelegate;

constexpr char kTestData[] = "Test Data";

class DataSourceTest : public testing::Test {
 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::MainThreadType::DEFAULT,
      base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC};
};

void CheckMimeType(const std::string& expected,
                   base::OnceClosure counter,
                   const std::string& mime_type,
                   const std::vector<uint8_t>& data) {
  EXPECT_FALSE(expected.empty());
  EXPECT_EQ(expected, mime_type);
  std::move(counter).Run();
}

void CheckTextMimeType(const std::string& expected,
                       base::OnceClosure counter,
                       const std::string& mime_type,
                       std::u16string data) {
  EXPECT_FALSE(expected.empty());
  EXPECT_EQ(expected, mime_type);
  std::move(counter).Run();
}

struct FileContents {
  std::string mime_type;
  std::string parsed_filename;
};

void CheckFileContentsMimeType(const FileContents& file_contents,
                               base::OnceClosure counter,
                               const std::string& mime_type,
                               const base::FilePath& filename,
                               const std::vector<uint8_t>& data) {
  EXPECT_FALSE(file_contents.mime_type.empty());
  EXPECT_EQ(file_contents.mime_type, mime_type);
  EXPECT_EQ(file_contents.parsed_filename, filename.value());
  std::move(counter).Run();
}

void CheckWebCustomDataMimeType(const std::string& expected,
                                base::OnceClosure counter,
                                const std::string& mime_type,
                                const std::vector<uint8_t>& data) {
  EXPECT_FALSE(mime_type.empty());
  EXPECT_EQ(expected, mime_type);
  std::move(counter).Run();
}

void IncrementFailureCounter(std::atomic_int* failure_count,
                             base::RepeatingClosure counter) {
  ++(*failure_count);
  std::move(counter).Run();
}

void CheckMimeTypesReceived(
    DataSource* data_source,
    const std::string& text_mime,
    const std::string& rtf_mime,
    const std::string& html_mime,
    const std::string& image_mime,
    const std::string& filenames_mime,
    const FileContents& file_contents,
    const std::string& web_custom_data_mime = std::string()) {
  base::RunLoop run_loop;
  base::RepeatingClosure counter =
      base::BarrierClosure(DataSource::kMaxDataTypes, run_loop.QuitClosure());
  std::atomic_int failure_count;
  failure_count.store(0);
  data_source->GetDataForPreferredMimeTypes(
      base::BindOnce(&CheckTextMimeType, text_mime, counter),
      base::BindOnce(&CheckMimeType, rtf_mime, counter),
      base::BindOnce(&CheckTextMimeType, html_mime, counter),
      base::BindOnce(&CheckMimeType, image_mime, counter),
      base::BindOnce(&CheckMimeType, filenames_mime, counter),
      base::BindOnce(&CheckFileContentsMimeType, file_contents, counter),
      base::BindOnce(&CheckWebCustomDataMimeType, web_custom_data_mime,
                     counter),
      base::BindRepeating(&IncrementFailureCounter, &failure_count, counter));
  run_loop.Run();

  int expected_failure_count = 0;
  for (const auto& mime_type :
       {text_mime, rtf_mime, html_mime, image_mime, filenames_mime,
        file_contents.mime_type, web_custom_data_mime}) {
    if (mime_type.empty())
      ++expected_failure_count;
  }
  EXPECT_EQ(expected_failure_count, failure_count.load());
}

TEST_F(DataSourceTest, ReadData) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  std::string mime_type("text/plain;charset=utf-8");
  delegate.SetData(mime_type, kTestData);
  data_source.Offer(mime_type.c_str());

  data_source.ReadDataForTesting(
      mime_type, base::BindOnce([](const std::string& mime_type,
                                   const std::vector<uint8_t>& data) {
        std::string string_data(data.begin(), data.end());
        EXPECT_EQ(std::string(kTestData), string_data);
      }));
  task_environment_.RunUntilIdle();
}

TEST_F(DataSourceTest, ReadDataArbitraryMimeType) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  std::string mime_type("abc/def;key=value");
  delegate.SetData(mime_type, kTestData);
  data_source.Offer(mime_type.c_str());

  data_source.ReadDataForTesting(
      mime_type, base::BindOnce([](const std::string& mime_type,
                                   const std::vector<uint8_t>& data) {
        std::string string_data(data.begin(), data.end());
        EXPECT_EQ(std::string(kTestData), string_data);
      }));
  task_environment_.RunUntilIdle();
}

TEST_F(DataSourceTest, ReadData_UnknownMimeType) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/plain;charset=utf-8");

  std::string unknown_type("text/unknown");
  data_source.ReadDataForTesting(
      unknown_type, base::BindOnce([](const std::string& mime_type,
                                      const std::vector<uint8_t>& data) {
        FAIL() << "Callback should not be invoked when known "
                  "mimetype is not offerred";
      }));
  task_environment_.RunUntilIdle();
}

TEST_F(DataSourceTest, ReadData_Destroyed) {
  TestDataSourceDelegate delegate;
  {
    DataSource data_source(&delegate);
    std::string mime_type("text/plain;charset=utf-8");
    data_source.Offer(mime_type);

    data_source.ReadDataForTesting(
        mime_type, base::BindOnce([](const std::string& mime_type,
                                     const std::vector<uint8_t>& data) {
          FAIL() << "Callback should not be invoked after "
                    "data source is destroyed";
        }));
  }
  task_environment_.RunUntilIdle();
}

TEST_F(DataSourceTest, ReadData_Cancelled) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  std::string mime_type("text/plain;charset=utf-8");
  data_source.Offer(mime_type);

  data_source.ReadDataForTesting(
      mime_type, base::BindOnce([](const std::string& mime_type,
                                   const std::vector<uint8_t>& data) {
        FAIL() << "Callback should not be invoked after cancelled";
      }));
  data_source.Cancelled();
  task_environment_.RunUntilIdle();
}

TEST_F(DataSourceTest, ReadData_Deleted) {
  TestDataSourceDelegate delegate;
  auto data_source = std::make_unique<DataSource>(&delegate);
  std::string mime_type("text/plain;charset=utf-8");
  data_source->Offer(mime_type);

  base::RunLoop run_loop;
  data_source->ReadDataForTesting(mime_type, base::DoNothing(),
                                  run_loop.QuitClosure());
  data_source.reset();
  run_loop.Run();
}

TEST_F(DataSourceTest, CheckDteMimeTypeReceived) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  const std::string kDteMimeType("chromium/x-data-transfer-endpoint");
  data_source.Offer(kDteMimeType);

  base::RunLoop run_loop;
  base::RepeatingClosure counter =
      base::BarrierClosure(1, run_loop.QuitClosure());
  std::atomic_int failure_count{0};

  data_source.ReadDataTransferEndpoint(
      base::BindOnce(&CheckTextMimeType, kDteMimeType, counter),
      base::BindRepeating(&IncrementFailureCounter, &failure_count, counter));

  run_loop.Run();
  EXPECT_EQ(0, failure_count.load());
}

TEST_F(DataSourceTest, PreferredMimeTypeUTF16) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/plain;charset=utf-16");
  data_source.Offer("text/plain;charset=UTF-8");
  data_source.Offer("text/html;charset=UTF-16");
  data_source.Offer("text/html;charset=utf-8");

  CheckMimeTypesReceived(&data_source, "text/plain;charset=utf-16", "",
                         "text/html;charset=UTF-16", "", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeUTF16LE) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/plain;charset=utf-16le");
  data_source.Offer("text/plain;charset=utf8");
  data_source.Offer("text/html;charset=utf16le");
  data_source.Offer("text/html;charset=utf-8");

  CheckMimeTypesReceived(&data_source, "text/plain;charset=utf-16le", "",
                         "text/html;charset=utf16le", "", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeUTF16BE) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/plain;charset=utf-16be");
  data_source.Offer("text/plain;charset=UTF8");
  data_source.Offer("text/html;charset=UTF16be");
  data_source.Offer("text/html;charset=utf-8");

  CheckMimeTypesReceived(&data_source, "text/plain;charset=utf-16be", "",
                         "text/html;charset=UTF16be", "", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeUTFToOther) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/plain;charset=utf-8");
  data_source.Offer("text/plain;charset=iso-8859-1");
  data_source.Offer("text/html;charset=utf-8");
  data_source.Offer("text/html;charset=iso-8859-1");

  CheckMimeTypesReceived(&data_source, "text/plain;charset=utf-8", "",
                         "text/html;charset=utf-8", "", "", {});
}

TEST_F(DataSourceTest, RecogniseUTF8Legaccy) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("UTF8_STRING");
  data_source.Offer("text/plain;charset=iso-8859-1");

  CheckMimeTypesReceived(&data_source, "UTF8_STRING", "", "", "", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeOtherToAscii) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/plain;charset=iso-8859-1");
  data_source.Offer("text/plain;charset=ASCII");
  data_source.Offer("text/html;charset=iso-8859-1");
  data_source.Offer("text/html;charset=ascii");

  CheckMimeTypesReceived(&data_source, "text/plain;charset=iso-8859-1", "",
                         "text/html;charset=iso-8859-1", "", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeOtherToUnspecified) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/plain;charset=iso-8859-1");
  data_source.Offer("text/plain");
  data_source.Offer("text/html;charset=iso-8859-1");
  data_source.Offer("text/html");

  CheckMimeTypesReceived(&data_source, "text/plain;charset=iso-8859-1", "",
                         "text/html;charset=iso-8859-1", "", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeRTF) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/rtf");

  CheckMimeTypesReceived(&data_source, "", "text/rtf", "", "", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypePNGtoBitmap) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("image/bmp");
  data_source.Offer("image/png");

  CheckMimeTypesReceived(&data_source, "", "", "", "image/png", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypePNGToJPEG) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("image/png");
  data_source.Offer("image/jpeg");
  data_source.Offer("image/jpg");

  CheckMimeTypesReceived(&data_source, "", "", "", "image/png", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeBitmaptoJPEG) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("image/bmp");
  data_source.Offer("image/jpeg");
  data_source.Offer("image/jpg");

  CheckMimeTypesReceived(&data_source, "", "", "", "image/bmp", "", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeTextUriList) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("text/uri-list");

  CheckMimeTypesReceived(&data_source, "", "", "", "", "text/uri-list", {});
}

TEST_F(DataSourceTest, PreferredMimeTypeOctetStream) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("application/octet-stream;name=test.jpg");

  CheckMimeTypesReceived(
      &data_source, "", "", "", "", "",
      {"application/octet-stream;name=test.jpg", "test.jpg"});
}

TEST_F(DataSourceTest, OctetStreamWithoutName) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("application/octet-stream");

  CheckMimeTypesReceived(&data_source, "", "", "", "", "", {});
}

TEST_F(DataSourceTest, OctetStreamWithQuotedName) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  data_source.Offer("application/octet-stream;name=\"t\\\\est\\\".jpg\"");

  CheckMimeTypesReceived(
      &data_source, "", "", "", "", "",
      {"application/octet-stream;name=\"t\\\\est\\\".jpg\"", "t\\est\".jpg"});
}

TEST_F(DataSourceTest, WebCustomDataMime) {
  TestDataSourceDelegate delegate;
  DataSource data_source(&delegate);
  std::string web_custom_data_mime("chromium/x-web-custom-data");
  data_source.Offer(web_custom_data_mime);

  CheckMimeTypesReceived(&data_source, "", "", "", "", "", {},
                         web_custom_data_mime);
}

}  // namespace
}  // namespace exo