/* Copyright (c) 2012 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. */ #ifndef CRAS_DBUS_TEST_H_ #define CRAS_DBUS_TEST_H_ #include #include #include #include #include #include /* DBusTest, and the related DBusMatch class, are used to provide a * GMock-like experience for testing D-Bus code within cras. * * It works by providing a connection to a private D-Bus Server for use * by code you intend to test. Before making calls, you set expectations * of method calls that the server should receive and reply to, or * instructions for the server to send signals that your connection * should receive and handle. * * The code style is similar to GMock for purposes of familiarity. * * Examples * -------- * * To create a test suite class implementing a SetUp and TearDown method, * be sure to call the base-class methods at the appropriate time. * * class ExampleTestSuite : public DBusTest { * virtual void SetUp() { * DBusTest::SetUp(); * // your setup code here * } * * virtual void TearDown() { * // your teardown code here * DBusTest::TearDown(); * } * }; * * To expect a method call to be made against the server; matching the * object path, interface and method name and then generating an empty * reply. The test code ensures that the reply is received during the * TearDown method. * * TEST_F(ExampleTestSuite, ExampleTest) { * ExpectMethodCall("/object/path", "object.Interface", "MethodName") * .SendReply(); * * // code to generate the method call here * } * * Due to the asynchronous nature of D-Bus, if you need to check some * state, it's not enough to immediately follow the code that generates * the method call. You must instead ensure that all expectations up to * that point have been met: * * TEST_F(ExampleTestSuite, ExampleTest) { * ExpectMethodCall("/object/path", "object.Interface", "MethodName") * .SendReply(); * * // code to generate the method call here * * WaitForMatches(); * * // code to examine state here * } * * To verify the arguments to method calls, place .With*() calls before * sending the reply: * * ExpectMethodCall("/object/path", "object.Interface", "MethodName") * .WithObjectPath("/arg0/object/path") * .WithString("arg1") * .WithString("arg2") * .SendReply(); * * Normally additional arguments are permitted, since most D-Bus services * don't go out of their way to check they aren't provided; to verify * there are no more arguments use .WithNoMoreArgs(): * * ExpectMethodCall("/object/path", "object.Interface", "MethodName") * .WithString("arg0") * .WithNoMoreArgs() * .SendReply(); * * To append arguments to the reply, place .With*() calls after the * instruction to send the reply: * * ExpectMethodCall("/object/path", "object.Interface", "MethodName") * .SendReply() * .WithString("arg0") * .WithObjectPath("/arg1/object/path"); * * Property dictionaries are sufficiently difficult to deal with that * there is special handling for them; to append one to the reply use * .AsPropertyDictionary() and follow with alternate .WithString() and * other .With*() calls for each property: * * ExpectMethodCall("/object/path", "object.Interface", "GetProperties") * .SendReply() * .AsPropertyDictionary() * .WithString("Keyword") * .WithObjectPath("/value/of/keyword"); * * To reply with an error use .SendError() instead of .SendReply(), * passing the error name and message * * ExpectMethodCall("/object/path", "object.Interface", "MethodName") * .SendError("some.error.Name", "Message for error"); * * In some cases (notably "AddMatch" method calls) the method call will * be handled by libdbus itself and the mechanism DBusTest uses to verify * that the reply is recieved does not work. In which case you need to use * .SendReplyNoWait() instead. * * ExpectMethodCall("", DBUS_INTERFACE_DBUS, "AddMatch") * .SendReplyNoWait(); * * Sending signals from the server side is very similar: * * CreateSignal("/object/path", "object.Interface", "SignalName") * .WithString("arg0") * .WithObjectPat("/arg1/object/path") * .Send(); * * Create messages from server side: * CreateMessageCall("/object/path". "object.Interface", "MethodName") * .WithString("arg0") * .WithUnixFd(arg1) * .Send(); * * The TearDown() method will verify that it is received by the client, * use WaitForMatches() to force verification earlier in order to check * state. */ class DBusTest; class DBusMatch { public: DBusMatch(); struct Arg { int type; bool array; std::string string_value; int int_value; std::vector string_values; }; // Append arguments to a match. DBusMatch& WithString(std::string value); DBusMatch& WithUnixFd(int value); DBusMatch& WithObjectPath(std::string value); DBusMatch& WithArrayOfStrings(std::vector values); DBusMatch& WithArrayOfObjectPaths(std::vector values); DBusMatch& WithNoMoreArgs(); // Indicates that all arguments in either the method call or reply // should be wrapped into a property dictionary with a string for keys // and a variant for the data. DBusMatch& AsPropertyDictionary(); // Send a reply to a method call and wait for it to be received by the // client; may be followed by methods to append arguments. DBusMatch& SendReply(); // Send an error in reply to a method call and wait for it to be received // by the client; may also be followed by methods to append arguments. DBusMatch& SendError(std::string error_name, std::string error_message); // Send a reply to a method call but do not wait for it to be received; // mostly needed for internal D-Bus messages. DBusMatch& SendReplyNoWait(); // Send a created signal. DBusMatch& Send(); private: friend class DBusTest; // Methods used by DBusTest after constructing the DBusMatch instance // to set the type of match. void ExpectMethodCall(std::string path, std::string interface, std::string method); void CreateSignal(DBusConnection* conn, std::string path, std::string interface, std::string signal_name); void CreateMessageCall(DBusConnection* conn, std::string path, std::string interface, std::string signal_name); // Determine whether a message matches a set of arguments. bool MatchMessageArgs(DBusMessage* message, std::vector* args); // Append a set of arguments to a message. void AppendArgsToMessage(DBusMessage* message, std::vector* args); // Send a message on a connection. void SendMessage(DBusConnection* conn, DBusMessage* message); // Handle a message received by the server connection. bool HandleServerMessage(DBusConnection* conn, DBusMessage* message); // Handle a message received by the client connection. bool HandleClientMessage(DBusConnection* conn, DBusMessage* message); // Verify whether the match is complete. bool Complete(); int message_type_; std::string path_; std::string interface_; std::string member_; bool as_property_dictionary_; std::vector args_; DBusConnection* conn_; bool send_reply_; std::vector reply_args_; bool send_error_; std::string error_name_; std::string error_message_; bool expect_serial_; std::vector expected_serials_; bool matched_; }; class DBusTest : public ::testing::Test { public: DBusTest(); virtual ~DBusTest(); protected: // Connection to the D-Bus server, this may be used during tests as the // "bus" connection, all messages go to and from the internal D-Bus server. DBusConnection* conn_; // Expect a method call to be received by the server. DBusMatch& ExpectMethodCall(std::string path, std::string interface, std::string method); // Send a signal from the client to the server. DBusMatch& CreateSignal(std::string path, std::string interface, std::string signal_name); // Send a message from the client to the server. DBusMatch& CreateMessageCall(std::string path, std::string interface, std::string signal_name); // Wait for all matches created by Expect*() or Create*() methods to // be complete. void WaitForMatches(); // When overriding be sure to call these parent methods to allow the // D-Bus server thread to be cleanly initialized and shut down. virtual void SetUp(); virtual void TearDown(); private: DBusServer* server_; DBusConnection* server_conn_; std::vector watches_; std::vector timeouts_; pthread_t thread_id_; pthread_mutex_t mutex_; bool dispatch_; std::vector matches_; static void NewConnectionThunk(DBusServer* server, DBusConnection* conn, void* data); void NewConnection(DBusServer* server, DBusConnection* conn); static dbus_bool_t AddWatchThunk(DBusWatch* watch, void* data); dbus_bool_t AddWatch(DBusWatch* watch); static void RemoveWatchThunk(DBusWatch* watch, void* data); void RemoveWatch(DBusWatch* watch); static void WatchToggledThunk(DBusWatch* watch, void* data); void WatchToggled(DBusWatch* watch); static dbus_bool_t AddTimeoutThunk(DBusTimeout* timeout, void* data); dbus_bool_t AddTimeout(DBusTimeout* timeout); static void RemoveTimeoutThunk(DBusTimeout* timeout, void* data); void RemoveTimeout(DBusTimeout* timeout); static void TimeoutToggledThunk(DBusTimeout* timeout, void* data); void TimeoutToggled(DBusTimeout* timeout); static DBusHandlerResult HandleMessageThunk(DBusConnection* conn, DBusMessage* message, void* data); DBusHandlerResult HandleMessage(DBusConnection* conn, DBusMessage* message); static void* DispatchLoopThunk(void* ptr); void* DispatchLoop(); void DispatchOnce(); }; #endif /* CRAS_DBUS_TEST_H_ */