chromium/chromecast/mojo/interface_bundle_test.cc

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

#include "chromecast/mojo/interface_bundle.h"

#include <string>

#include "base/functional/bind.h"
#include "base/test/task_environment.h"
#include "chromecast/mojo/remote_interfaces.h"
#include "chromecast/mojo/test/test_interfaces.test-mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;

namespace chromecast {

class MockInterface : public test::mojom::StringInterface,
                      public test::mojom::IntInterface,
                      public test::mojom::BoolInterface {
 public:
  MOCK_METHOD(void, StringMethod, (const std::string& s), (override));
  MOCK_METHOD(void, IntMethod, (int32_t i), (override));
  MOCK_METHOD(void, BoolMethod, (bool b), (override));
};

class MockErrorHandler {
 public:
  MOCK_METHOD(void, OnError, ());
};

class InterfaceBundleTest : public testing::Test {
 protected:
  InterfaceBundleTest() {}

  base::test::TaskEnvironment task_environment_;

  MockInterface mock_interface_;
};

TEST_F(InterfaceBundleTest, ExampleUsage) {
  InterfaceBundle bundle;
  RemoteInterfaces interfaces(bundle.CreateRemote());

  // ===========================================================================
  // Success Scenarios
  // ===========================================================================
  // Add an implementation of StringInterface to this bundle.
  ASSERT_TRUE(
      bundle.AddInterface<test::mojom::StringInterface>(&mock_interface_));

  // RemoteInterfaces dispenses Remotes on behalf of InterfaceBundle.
  mojo::Remote<test::mojom::StringInterface> remote;
  interfaces.BindNewPipe(&remote);
  ASSERT_TRUE(remote.is_bound());

  // The newly created Remote will call into the implementation.
  std::string s = "Hello, world!";
  EXPECT_CALL(mock_interface_, StringMethod(s));
  remote->StringMethod(s);
  task_environment_.RunUntilIdle();

  // ===========================================================================
  // Error Scenarios
  // ===========================================================================
  // IntInterface wasn't provided, but we allow a new Remote to be created.
  // However, once the request is rejected by InterfaceBundle, the Remote will
  // be disconnected.
  auto bad_remote = interfaces.CreateRemote<test::mojom::IntInterface>();
  ASSERT_TRUE(bad_remote.is_bound());
  ASSERT_TRUE(bad_remote.is_connected());

  task_environment_.RunUntilIdle();
  ASSERT_FALSE(bad_remote.is_connected());
}

TEST_F(InterfaceBundleTest, Lifetime) {
  MockErrorHandler error_handler_;
  mojo::Remote<test::mojom::StringInterface> remote;
  {
    InterfaceBundle bundle;
    RemoteInterfaces interfaces(bundle.CreateRemote());

    ASSERT_TRUE(
        bundle.AddInterface<test::mojom::StringInterface>(&mock_interface_));
    interfaces.BindNewPipe(&remote);

    // Verify the Remote is usable.
    std::string s = "Hello, world!";
    EXPECT_CALL(mock_interface_, StringMethod(s));
    remote->StringMethod(s);
    task_environment_.RunUntilIdle();

    remote.set_disconnect_handler(base::BindOnce(
        &MockErrorHandler::OnError, base::Unretained(&error_handler_)));

    // When the InterfaceBundle is destroyed, all dispensed Remotes will be
    // invalidated.
    EXPECT_CALL(error_handler_, OnError()).Times(1);
  }
  task_environment_.RunUntilIdle();
  ASSERT_FALSE(remote.is_connected());
}

TEST_F(InterfaceBundleTest, LateBinding) {
  InterfaceBundle bundle;
  // Don't link to the InterfaceBundle just yet.
  RemoteInterfaces interfaces;

  ASSERT_TRUE(
      bundle.AddInterface<test::mojom::StringInterface>(&mock_interface_));

  mojo::Remote<test::mojom::StringInterface> remote;
  interfaces.BindNewPipe(&remote);
  std::string s = "Hello, world!";

  // We do not have a link to an implementation yet, but the client can still
  // try to use the interface anyway.
  EXPECT_CALL(mock_interface_, StringMethod(s)).Times(0);
  remote->StringMethod(s);
  remote->StringMethod(s);
  remote->StringMethod(s);
  task_environment_.RunUntilIdle();

  // When we bind the provider late, all requests should be fulfilled.
  EXPECT_CALL(mock_interface_, StringMethod(s)).Times(3);
  interfaces.SetProvider(bundle.CreateRemote());
  task_environment_.RunUntilIdle();
}

}  // namespace chromecast