summaryrefslogtreecommitdiffstats
path: root/src/tests/common_dbus.c
blob: 3117c080dc3106517bee933a458583f35b04fa63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
   SSSD

   Common utilities for dbus based tests.

   Authors:
        Stef Walter <stefw@redhat.com>

   Copyright (C) Red Hat, Inc 2014

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "config.h"

#include <stdio.h>
#include "tests/common.h"

struct mock_server {
    char *temp_dir;
    char *dbus_address;
    pid_t pid;
    DBusConnection *client;

    /* Used for synchronization */
    int sync_fds[2];

    /* Only used during init */
    sbus_server_conn_init_fn init_fn;
    void *init_pvt_data;
};

/*
 * If you think we're going to do full error propagation during tests ...
 * you're going to have a bad time (reading this code)
 */
#define verify_eq(x, y) \
    do { if ((x) != (y)) { fprintf(stderr, "failed: %s == %s\n", #x, #y); abort(); } } while (0)
#define verify_neq(x, y) \
    do { if ((x) == (y)) { fprintf(stderr, "failed: %s != %s\n", #x, #y); abort(); } } while (0)

static int
mock_server_cleanup(struct mock_server *mock)
{
    int child_status;
    const char *file;
    struct stat sb;

    dbus_connection_close(mock->client);
    dbus_connection_unref(mock->client);

    /* Tell the server thread to quit */
    verify_eq (write(mock->sync_fds[0], "X", 1), 1);

    /* Wait for the server child, it always returns mock */
    verify_eq (waitpid(mock->pid, &child_status, 0), mock->pid);
    verify_eq (child_status, 0);

    file = strchr(mock->dbus_address, '/');
    if (stat(file, &sb) == 0) {
        verify_eq (unlink(file), 0);
    }
    verify_eq (rmdir(mock->temp_dir), 0);

    return EOK;
}

static int
on_accept_connection(struct sbus_connection *conn,
                     void *data)
{
    struct mock_server *mock = data;

    verify_eq (mock->init_fn(conn, mock->init_pvt_data), EOK);

    /* Synchronization point: test_dbus_setup_mock() should return */
    verify_eq (write(mock->sync_fds[1], "X", 1), 1);

    return EOK;
}

static void
on_sync_fd_written(struct tevent_context *loop,
                   struct tevent_fd *fde,
                   uint16_t flags,
                   void *data)
{
    bool *stop_server = data;
    *stop_server = true;
}

static void
mock_server_child(void *data)
{
    struct mock_server *mock = data;
    struct tevent_context *loop;
    struct sbus_connection *server;
    bool stop_server = false;
    TALLOC_CTX *ctx;

    ctx = talloc_new(NULL);
    loop = tevent_context_init(ctx);

    verify_eq (sbus_new_server(ctx, loop, mock->dbus_address, false,
                               &server, on_accept_connection, mock), EOK);

    tevent_add_fd(loop, ctx, mock->sync_fds[1], TEVENT_FD_READ,
                  on_sync_fd_written, &stop_server);

    /* Synchronization point: test_dbus_setup_mock() should connect */
    verify_eq (write(mock->sync_fds[1], "X", 1), 1);

    /* Do the loop */
    while(!stop_server) {
        verify_eq (tevent_loop_once(loop), 0);
    }

    /* TODO: sbus doesn't support cleanup of a server */

    talloc_free(ctx);
}

struct DBusConnection *
test_dbus_setup_mock(TALLOC_CTX *mem_ctx,
                     struct tevent_context *loop,
                     sbus_server_conn_init_fn init_fn,
                     void *init_pvt_data)
{
    struct mock_server *mock;
    char dummy;

    mock = talloc_zero(mem_ctx, struct mock_server);
    talloc_set_destructor(mock, mock_server_cleanup);
    mock->init_fn = init_fn;
    mock->init_pvt_data = init_pvt_data;

    mock->temp_dir = mkdtemp(talloc_strdup(mock, "/tmp/sssd-dbus-tests.XXXXXX"));
    verify_neq (mock->temp_dir, NULL);
    mock->dbus_address = talloc_asprintf(mock, "unix:path=%s/sbus", mock->temp_dir);
    verify_neq (mock->dbus_address, NULL);

    /* We use an fd pair as a synchronization device, integrates with tevent well */
    verify_eq (socketpair(PF_LOCAL, SOCK_STREAM, 0, mock->sync_fds), 0);

    /* Run the dbus server in a child process */
    mock->pid = fork();
    if (mock->pid == 0) {
        mock_server_child(mock);
        _exit(0);
    }

    verify_neq (mock->pid, -1);

    /* Synchronization point: wait for sync point in mock_server_child */
    verify_eq (read(mock->sync_fds[0], &dummy, 1), 1);

    /* Open a shared D-BUS connection to the address */
    mock->client = dbus_connection_open_private(mock->dbus_address, NULL);
    verify_neq (mock->client, NULL);

    /* Synchronization point: wait for sync point in on_accept_connection */
    verify_eq (read(mock->sync_fds[0], &dummy, 1), 1);

    return mock->client;
}

DBusMessage *
test_dbus_call_sync(DBusConnection *conn, const char *object_path,
                    const char *interface, const char *method,
                    DBusError *error, int first_arg_type, ...)
{
    DBusMessage *message;
    DBusMessage *reply;
    va_list va;

    message = dbus_message_new_method_call(NULL, object_path, interface, method);
    verify_neq(message, NULL);

    va_start(va, first_arg_type);
    verify_eq(dbus_message_append_args_valist(message, first_arg_type, va), TRUE);
    va_end(va);

    reply = dbus_connection_send_with_reply_and_block(conn, message, -1, error);
    dbus_message_unref(message);

    return reply;
}