blob: b31c347299707cba242619c3dc6915f295bee9cb [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __TESTS_MESOS_HPP__
#define __TESTS_MESOS_HPP__
#include <map>
#include <set>
#include <string>
#include <mesos/executor.hpp>
#include <mesos/scheduler.hpp>
#include <process/future.hpp>
#include <process/gmock.hpp>
#include <process/gtest.hpp>
#include <process/owned.hpp>
#include <process/pid.hpp>
#include <process/process.hpp>
#include <stout/gtest.hpp>
#include <stout/lambda.hpp>
#include <stout/none.hpp>
#include <stout/option.hpp>
#include <stout/stringify.hpp>
#include <stout/try.hpp>
#include <stout/uuid.hpp>
#include "authorizer/authorizer.hpp"
#include "messages/messages.hpp" // For google::protobuf::Message.
#include "master/allocator.hpp"
#include "master/detector.hpp"
#include "master/hierarchical_allocator_process.hpp"
#include "master/master.hpp"
#include "slave/slave.hpp"
#include "slave/containerizer/containerizer.hpp"
#include "slave/containerizer/mesos/containerizer.hpp"
#include "tests/cluster.hpp"
#include "tests/utils.hpp"
#ifdef MESOS_HAS_JAVA
#include "tests/zookeeper.hpp"
#endif // MESOS_HAS_JAVA
namespace mesos {
namespace internal {
namespace tests {
// Forward declarations.
class MockExecutor;
class MesosTest : public TemporaryDirectoryTest
{
protected:
MesosTest(const Option<zookeeper::URL>& url = None());
virtual void TearDown();
// Returns the flags used to create masters.
virtual master::Flags CreateMasterFlags();
// Returns the flags used to create slaves.
virtual slave::Flags CreateSlaveFlags();
// Starts a master with the specified flags.
virtual Try<process::PID<master::Master> > StartMaster(
const Option<master::Flags>& flags = None());
// Starts a master with the specified allocator process and flags.
virtual Try<process::PID<master::Master> > StartMaster(
master::allocator::AllocatorProcess* allocator,
const Option<master::Flags>& flags = None());
// Starts a master with the specified authorizer and flags.
// Waits for the master to detect a leader (could be itself) before
// returning if 'wait' is set to true.
virtual Try<process::PID<master::Master> > StartMaster(
Authorizer* authorizer,
const Option<master::Flags>& flags = None());
// Starts a slave with the specified flags.
virtual Try<process::PID<slave::Slave> > StartSlave(
const Option<slave::Flags>& flags = None());
// Starts a slave with the specified mock executor and flags.
virtual Try<process::PID<slave::Slave> > StartSlave(
MockExecutor* executor,
const Option<slave::Flags>& flags = None());
// Starts a slave with the specified containerizer and flags.
virtual Try<process::PID<slave::Slave> > StartSlave(
slave::Containerizer* containerizer,
const Option<slave::Flags>& flags = None());
// Starts a slave with the specified containerizer, detector and flags.
virtual Try<process::PID<slave::Slave> > StartSlave(
slave::Containerizer* containerizer,
MasterDetector* detector,
const Option<slave::Flags>& flags = None());
// Starts a slave with the specified MasterDetector and flags.
virtual Try<process::PID<slave::Slave> > StartSlave(
MasterDetector* detector,
const Option<slave::Flags>& flags = None());
// Starts a slave with the specified mock executor, MasterDetector
// and flags.
virtual Try<process::PID<slave::Slave> > StartSlave(
MockExecutor* executor,
MasterDetector* detector,
const Option<slave::Flags>& flags = None());
// Stop the specified master.
virtual void Stop(
const process::PID<master::Master>& pid);
// Stop the specified slave.
virtual void Stop(
const process::PID<slave::Slave>& pid,
bool shutdown = false);
// Stop all masters and slaves.
virtual void Shutdown();
// Stop all masters.
virtual void ShutdownMasters();
// Stop all slaves.
virtual void ShutdownSlaves();
Cluster cluster;
// Containerizer(s) created during test that we need to cleanup.
std::map<process::PID<slave::Slave>, slave::Containerizer*> containerizers;
};
template <typename T>
class ContainerizerTest : public MesosTest {};
#ifdef __linux__
// Cgroups hierarchy used by the cgroups related tests.
const static std::string TEST_CGROUPS_HIERARCHY = "/tmp/mesos_test_cgroup";
// Name of the root cgroup used by the cgroups related tests.
const static std::string TEST_CGROUPS_ROOT = "mesos_test";
template <>
class ContainerizerTest<slave::MesosContainerizer> : public MesosTest
{
public:
static void SetUpTestCase();
static void TearDownTestCase();
protected:
virtual slave::Flags CreateSlaveFlags();
virtual void SetUp();
virtual void TearDown();
private:
// Base hierarchy for separately mounted cgroup controllers, e.g., if the
// base hierachy is /sys/fs/cgroup then each controller will be mounted to
// /sys/fs/cgroup/{controller}/.
std::string baseHierarchy;
// Set of cgroup subsystems used by the cgroups related tests.
hashset<std::string> subsystems;
};
#else
template<>
class ContainerizerTest<slave::MesosContainerizer> : public MesosTest
{
protected:
virtual slave::Flags CreateSlaveFlags();
};
#endif // __linux__
#ifdef MESOS_HAS_JAVA
class MesosZooKeeperTest : public MesosTest
{
public:
static void SetUpTestCase()
{
// Make sure the JVM is created.
ZooKeeperTest::SetUpTestCase();
// Launch the ZooKeeper test server.
server = new ZooKeeperTestServer();
server->startNetwork();
Try<zookeeper::URL> parse = zookeeper::URL::parse(
"zk://" + server->connectString() + "/znode");
ASSERT_SOME(parse);
url = parse.get();
}
static void TearDownTestCase()
{
delete server;
server = NULL;
}
protected:
MesosZooKeeperTest() : MesosTest(url) {}
virtual master::Flags CreateMasterFlags()
{
master::Flags flags = MesosTest::CreateMasterFlags();
// NOTE: Since we are using the replicated log with ZooKeeper
// (default storage in MesosTest), we need to specify the quorum.
flags.quorum = 1;
return flags;
}
static ZooKeeperTestServer* server;
static Option<zookeeper::URL> url;
};
#endif // MESOS_HAS_JAVA
// Macros to get/create (default) ExecutorInfos and FrameworkInfos.
#define DEFAULT_EXECUTOR_INFO \
({ ExecutorInfo executor; \
executor.mutable_executor_id()->set_value("default"); \
executor.mutable_command()->set_value("exit 1"); \
executor; })
#define CREATE_EXECUTOR_INFO(executorId, command) \
({ ExecutorInfo executor; \
executor.mutable_executor_id()->set_value(executorId); \
executor.mutable_command()->set_value(command); \
executor; })
#define DEFAULT_CREDENTIAL \
({ Credential credential; \
credential.set_principal("test-principal"); \
credential.set_secret("test-secret"); \
credential; })
#define DEFAULT_FRAMEWORK_INFO \
({ FrameworkInfo framework; \
framework.set_name("default"); \
framework.set_principal(DEFAULT_CREDENTIAL.principal()); \
framework; })
#define DEFAULT_EXECUTOR_ID \
DEFAULT_EXECUTOR_INFO.executor_id()
#define CREATE_COMMAND_INFO(command) \
({ CommandInfo commandInfo; \
commandInfo.set_value(command); \
commandInfo; })
// TODO(bmahler): Refactor this to make the distinction between
// command tasks and executor tasks clearer.
inline TaskInfo createTask(
const SlaveID& slaveId,
const Resources& resources,
const std::string& command,
const Option<mesos::ExecutorID>& executorId = None(),
const std::string& name = "test-task",
const std::string& id = UUID::random().toString())
{
TaskInfo task;
task.set_name(name);
task.mutable_task_id()->set_value(id);
task.mutable_slave_id()->CopyFrom(slaveId);
task.mutable_resources()->CopyFrom(resources);
if (executorId.isSome()) {
ExecutorInfo executor;
executor.mutable_executor_id()->CopyFrom(executorId.get());
executor.mutable_command()->set_value(command);
task.mutable_executor()->CopyFrom(executor);
} else {
task.mutable_command()->set_value(command);
}
return task;
}
inline TaskInfo createTask(
const Offer& offer,
const std::string& command,
const Option<mesos::ExecutorID>& executorId = None(),
const std::string& name = "test-task",
const std::string& id = UUID::random().toString())
{
return createTask(
offer.slave_id(), offer.resources(), command, executorId, name, id);
}
// Definition of a mock Scheduler to be used in tests with gmock.
class MockScheduler : public Scheduler
{
public:
MOCK_METHOD3(registered, void(SchedulerDriver*,
const FrameworkID&,
const MasterInfo&));
MOCK_METHOD2(reregistered, void(SchedulerDriver*, const MasterInfo&));
MOCK_METHOD1(disconnected, void(SchedulerDriver*));
MOCK_METHOD2(resourceOffers, void(SchedulerDriver*,
const std::vector<Offer>&));
MOCK_METHOD2(offerRescinded, void(SchedulerDriver*, const OfferID&));
MOCK_METHOD2(statusUpdate, void(SchedulerDriver*, const TaskStatus&));
MOCK_METHOD4(frameworkMessage, void(SchedulerDriver*,
const ExecutorID&,
const SlaveID&,
const std::string&));
MOCK_METHOD2(slaveLost, void(SchedulerDriver*, const SlaveID&));
MOCK_METHOD4(executorLost, void(SchedulerDriver*,
const ExecutorID&,
const SlaveID&,
int));
MOCK_METHOD2(error, void(SchedulerDriver*, const std::string&));
};
// For use with a MockScheduler, for example:
// EXPECT_CALL(sched, resourceOffers(_, _))
// .WillOnce(LaunchTasks(EXECUTOR, TASKS, CPUS, MEM, ROLE));
// Launches up to TASKS no-op tasks, if possible,
// each with CPUS cpus and MEM memory and EXECUTOR executor.
ACTION_P5(LaunchTasks, executor, tasks, cpus, mem, role)
{
SchedulerDriver* driver = arg0;
std::vector<Offer> offers = arg1;
int numTasks = tasks;
int launched = 0;
for (size_t i = 0; i < offers.size(); i++) {
const Offer& offer = offers[i];
const Resources TASK_RESOURCES = Resources::parse(
"cpus:" + stringify(cpus) + ";mem:" + stringify(mem)).get();
int nextTaskId = 0;
std::vector<TaskInfo> tasks;
Resources remaining = offer.resources();
while (TASK_RESOURCES <= remaining.flatten() && launched < numTasks) {
TaskInfo task;
task.set_name("TestTask");
task.mutable_task_id()->set_value(stringify(nextTaskId++));
task.mutable_slave_id()->MergeFrom(offer.slave_id());
task.mutable_executor()->MergeFrom(executor);
Option<Resources> resources = remaining.find(TASK_RESOURCES, role);
CHECK_SOME(resources);
task.mutable_resources()->MergeFrom(resources.get());
remaining -= resources.get();
tasks.push_back(task);
launched++;
}
driver->launchTasks(offer.id(), tasks);
}
}
// Like LaunchTasks, but decline the entire offer and
// don't launch any tasks.
ACTION(DeclineOffers)
{
SchedulerDriver* driver = arg0;
std::vector<Offer> offers = arg1;
for (size_t i = 0; i < offers.size(); i++) {
driver->declineOffer(offers[i].id());
}
}
// Like DeclineOffers, but takes a custom filters object.
ACTION_P(DeclineOffers, filters)
{
SchedulerDriver* driver = arg0;
std::vector<Offer> offers = arg1;
for (size_t i = 0; i < offers.size(); i++) {
driver->declineOffer(offers[i].id(), filters);
}
}
// Definition of a mock Executor to be used in tests with gmock.
class MockExecutor : public Executor
{
public:
MockExecutor(const ExecutorID& _id) : id(_id) {}
MOCK_METHOD4(registered, void(ExecutorDriver*,
const ExecutorInfo&,
const FrameworkInfo&,
const SlaveInfo&));
MOCK_METHOD2(reregistered, void(ExecutorDriver*, const SlaveInfo&));
MOCK_METHOD1(disconnected, void(ExecutorDriver*));
MOCK_METHOD2(launchTask, void(ExecutorDriver*, const TaskInfo&));
MOCK_METHOD2(killTask, void(ExecutorDriver*, const TaskID&));
MOCK_METHOD2(frameworkMessage, void(ExecutorDriver*, const std::string&));
MOCK_METHOD1(shutdown, void(ExecutorDriver*));
MOCK_METHOD2(error, void(ExecutorDriver*, const std::string&));
const ExecutorID id;
};
class TestingMesosSchedulerDriver : public MesosSchedulerDriver
{
public:
TestingMesosSchedulerDriver(
Scheduler* scheduler,
const FrameworkInfo& framework,
const Credential& credential,
MasterDetector* _detector)
: MesosSchedulerDriver(scheduler, framework, "", credential)
{
detector = _detector;
}
// A constructor that uses the DEFAULT_FRAMEWORK_INFO &
// DEFAULT_CREDENTIAL.
TestingMesosSchedulerDriver(
Scheduler* scheduler,
MasterDetector* _detector)
: MesosSchedulerDriver(
scheduler,
DEFAULT_FRAMEWORK_INFO,
"",
DEFAULT_CREDENTIAL)
{
detector = _detector;
}
~TestingMesosSchedulerDriver()
{
// This is necessary because in the base class the detector is
// internally created and deleted whereas in the testing driver
// it is injected and thus should not be deleted in the
// destructor. Setting it to null allows the detector to survive
// MesosSchedulerDriver::~MesosSchedulerDriver().
detector = NULL;
}
};
// Definition of a MockAuthozier that can be used in tests with gmock.
class MockAuthorizer : public Authorizer
{
public:
MockAuthorizer()
{
using ::testing::An;
using ::testing::Return;
// NOTE: We use 'EXPECT_CALL' and 'WillRepeatedly' here instead of
// 'ON_CALL' and 'WillByDefault'. See 'TestContainerizer::SetUp()'
// for more details.
EXPECT_CALL(*this, authorize(An<const mesos::ACL::RegisterFramework&>()))
.WillRepeatedly(Return(true));
EXPECT_CALL(*this, authorize(An<const mesos::ACL::RunTask&>()))
.WillRepeatedly(Return(true));
EXPECT_CALL(*this, authorize(An<const mesos::ACL::ShutdownFramework&>()))
.WillRepeatedly(Return(true));
}
MOCK_METHOD1(
authorize, process::Future<bool>(const ACL::RegisterFramework& request));
MOCK_METHOD1(
authorize, process::Future<bool>(const ACL::RunTask& request));
MOCK_METHOD1(
authorize, process::Future<bool>(const ACL::ShutdownFramework& request));
};
template <typename T = master::allocator::AllocatorProcess>
class MockAllocatorProcess : public master::allocator::AllocatorProcess
{
public:
MockAllocatorProcess()
{
// Spawn the underlying allocator process.
process::spawn(real);
using ::testing::_;
ON_CALL(*this, initialize(_, _, _))
.WillByDefault(InvokeInitialize(this));
ON_CALL(*this, frameworkAdded(_, _, _))
.WillByDefault(InvokeFrameworkAdded(this));
ON_CALL(*this, frameworkRemoved(_))
.WillByDefault(InvokeFrameworkRemoved(this));
ON_CALL(*this, frameworkActivated(_, _))
.WillByDefault(InvokeFrameworkActivated(this));
ON_CALL(*this, frameworkDeactivated(_))
.WillByDefault(InvokeFrameworkDeactivated(this));
ON_CALL(*this, slaveAdded(_, _, _))
.WillByDefault(InvokeSlaveAdded(this));
ON_CALL(*this, slaveRemoved(_))
.WillByDefault(InvokeSlaveRemoved(this));
ON_CALL(*this, slaveDeactivated(_))
.WillByDefault(InvokeSlaveDeactivated(this));
ON_CALL(*this, slaveActivated(_))
.WillByDefault(InvokeSlaveReactivated(this));
ON_CALL(*this, updateWhitelist(_))
.WillByDefault(InvokeUpdateWhitelist(this));
ON_CALL(*this, resourcesRequested(_, _))
.WillByDefault(InvokeResourcesRequested(this));
ON_CALL(*this, resourcesRecovered(_, _, _, _))
.WillByDefault(InvokeResourcesRecovered(this));
ON_CALL(*this, offersRevived(_))
.WillByDefault(InvokeOffersRevived(this));
}
~MockAllocatorProcess()
{
process::terminate(real);
process::wait(real);
}
MOCK_METHOD3(initialize, void(const master::Flags&,
const process::PID<master::Master>&,
const hashmap<std::string, RoleInfo>&));
MOCK_METHOD3(frameworkAdded, void(const FrameworkID&,
const FrameworkInfo&,
const Resources&));
MOCK_METHOD1(frameworkRemoved, void(const FrameworkID&));
MOCK_METHOD2(frameworkActivated, void(const FrameworkID&,
const FrameworkInfo&));
MOCK_METHOD1(frameworkDeactivated, void(const FrameworkID&));
MOCK_METHOD3(slaveAdded, void(const SlaveID&,
const SlaveInfo&,
const hashmap<FrameworkID, Resources>&));
MOCK_METHOD1(slaveRemoved, void(const SlaveID&));
MOCK_METHOD1(slaveDeactivated, void(const SlaveID&));
MOCK_METHOD1(slaveActivated, void(const SlaveID&));
MOCK_METHOD1(updateWhitelist, void(const Option<hashset<std::string> >&));
MOCK_METHOD2(resourcesRequested, void(const FrameworkID&,
const std::vector<Request>&));
MOCK_METHOD4(resourcesRecovered, void(const FrameworkID&,
const SlaveID&,
const Resources&,
const Option<Filters>& filters));
MOCK_METHOD1(offersRevived, void(const FrameworkID&));
T real;
};
typedef ::testing::Types<master::allocator::HierarchicalDRFAllocatorProcess>
AllocatorTypes;
// The following actions make up for the fact that DoDefault
// cannot be used inside a DoAll, for example:
// EXPECT_CALL(allocator, frameworkAdded(_, _, _))
// .WillOnce(DoAll(InvokeFrameworkAdded(&allocator),
// FutureSatisfy(&frameworkAdded)));
ACTION_P(InvokeInitialize, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::initialize,
arg0,
arg1,
arg2);
}
ACTION_P(InvokeFrameworkAdded, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::frameworkAdded,
arg0,
arg1,
arg2);
}
ACTION_P(InvokeFrameworkRemoved, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::frameworkRemoved, arg0);
}
ACTION_P(InvokeFrameworkActivated, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::frameworkActivated,
arg0,
arg1);
}
ACTION_P(InvokeFrameworkDeactivated, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::frameworkDeactivated,
arg0);
}
ACTION_P(InvokeSlaveAdded, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::slaveAdded,
arg0,
arg1,
arg2);
}
ACTION_P(InvokeSlaveRemoved, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::slaveRemoved,
arg0);
}
ACTION_P(InvokeSlaveDeactivated, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::slaveDeactivated,
arg0);
}
ACTION_P(InvokeSlaveReactivated, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::slaveActivated,
arg0);
}
ACTION_P(InvokeUpdateWhitelist, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::updateWhitelist,
arg0);
}
ACTION_P(InvokeResourcesRequested, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::resourcesRequested,
arg0,
arg1);
}
ACTION_P(InvokeResourcesRecovered, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::resourcesRecovered,
arg0,
arg1,
arg2,
arg3);
}
ACTION_P2(InvokeResourcesRecoveredWithFilters, allocator, timeout)
{
Filters filters;
filters.set_refuse_seconds(timeout);
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::resourcesRecovered,
arg0,
arg1,
arg2,
filters);
}
ACTION_P(InvokeOffersRevived, allocator)
{
process::dispatch(
allocator->real,
&master::allocator::AllocatorProcess::offersRevived,
arg0);
}
class OfferEqMatcher
: public ::testing::MatcherInterface<const std::vector<Offer>& >
{
public:
OfferEqMatcher(int _cpus, int _mem)
: cpus(_cpus), mem(_mem) {}
virtual bool MatchAndExplain(const std::vector<Offer>& offers,
::testing::MatchResultListener* listener) const
{
double totalCpus = 0;
double totalMem = 0;
foreach (const Offer& offer, offers) {
foreach (const Resource& resource, offer.resources()) {
if (resource.name() == "cpus") {
totalCpus += resource.scalar().value();
} else if (resource.name() == "mem") {
totalMem += resource.scalar().value();
}
}
}
bool matches = totalCpus == cpus && totalMem == mem;
if (!matches) {
*listener << totalCpus << " cpus and " << totalMem << "mem";
}
return matches;
}
virtual void DescribeTo(::std::ostream* os) const
{
*os << "contains " << cpus << " cpus and " << mem << " mem";
}
virtual void DescribeNegationTo(::std::ostream* os) const
{
*os << "does not contain " << cpus << " cpus and " << mem << " mem";
}
private:
int cpus;
int mem;
};
inline
const ::testing::Matcher<const std::vector<Offer>& > OfferEq(int cpus, int mem)
{
return MakeMatcher(new OfferEqMatcher(cpus, mem));
}
// Definition of the SendStatusUpdateFromTask action to be used with gmock.
ACTION_P(SendStatusUpdateFromTask, state)
{
TaskStatus status;
status.mutable_task_id()->MergeFrom(arg1.task_id());
status.set_state(state);
arg0->sendStatusUpdate(status);
}
// Definition of the SendStatusUpdateFromTaskID action to be used with gmock.
ACTION_P(SendStatusUpdateFromTaskID, state)
{
TaskStatus status;
status.mutable_task_id()->MergeFrom(arg1);
status.set_state(state);
arg0->sendStatusUpdate(status);
}
#define FUTURE_PROTOBUF(message, from, to) \
FutureProtobuf(message, from, to)
#define DROP_PROTOBUF(message, from, to) \
FutureProtobuf(message, from, to, true)
#define DROP_PROTOBUFS(message, from, to) \
DropProtobufs(message, from, to)
#define EXPECT_NO_FUTURE_PROTOBUFS(message, from, to) \
ExpectNoFutureProtobufs(message, from, to)
// Forward declaration.
template <typename T>
T _FutureProtobuf(const process::Message& message);
template <typename T, typename From, typename To>
process::Future<T> FutureProtobuf(T t, From from, To to, bool drop = false)
{
// Help debugging by adding some "type constraints".
{ google::protobuf::Message* m = &t; (void) m; }
return process::FutureMessage(testing::Eq(t.GetTypeName()), from, to, drop)
.then(lambda::bind(&_FutureProtobuf<T>, lambda::_1));
}
template <typename T>
T _FutureProtobuf(const process::Message& message)
{
T t;
t.ParseFromString(message.body);
return t;
}
template <typename T, typename From, typename To>
void DropProtobufs(T t, From from, To to)
{
// Help debugging by adding some "type constraints".
{ google::protobuf::Message* m = &t; (void) m; }
process::DropMessages(testing::Eq(t.GetTypeName()), from, to);
}
template <typename T, typename From, typename To>
void ExpectNoFutureProtobufs(T t, From from, To to)
{
// Help debugging by adding some "type constraints".
{ google::protobuf::Message* m = &t; (void) m; }
process::ExpectNoFutureMessages(testing::Eq(t.GetTypeName()), from, to);
}
} // namespace tests {
} // namespace internal {
} // namespace mesos {
#endif // __TESTS_MESOS_HPP__