]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - tests/src/test-runner.c
Add tests for login with invalid sessions, and autologin with session/xserver crash
[sojka/lightdm.git] / tests / src / test-runner.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <ctype.h>
5 #include <errno.h>
6 #include <glib.h>
7 #include <gio/gio.h>
8 #include <unistd.h>
9 #include <sys/types.h>
10 #include <sys/socket.h>
11 #include <sys/un.h>
12 #include <sys/wait.h>
13
14 /* For some reason sys/un.h doesn't define this */
15 #ifndef UNIX_PATH_MAX
16 #define UNIX_PATH_MAX 108
17 #endif
18
19 static GPid lightdm_pid = 0;
20 static gchar *status_socket_name = NULL;
21 static gboolean expect_exit = FALSE;
22 static GList *statuses = NULL;
23 static GList *script = NULL;
24 static GList *script_iter = NULL;
25 static guint status_timeout = 0;
26 static gboolean failed = FALSE;
27 static gchar *temp_dir = NULL;
28 static GList *children = NULL;
29
30 static void check_status (const gchar *status);
31
32 static void
33 stop_daemon ()
34 {
35     if (lightdm_pid)
36         kill (lightdm_pid, SIGTERM);
37 }
38
39 static void
40 quit (int status)
41 {
42     GList *link;
43
44     stop_daemon ();
45     if (status_socket_name)
46         unlink (status_socket_name);
47
48     for (link = children; link; link = link->next)
49     {
50         GPid pid = GPOINTER_TO_INT (link->data);
51         kill (pid, SIGTERM);
52     }
53
54     if (temp_dir)
55     {
56         gchar *command = g_strdup_printf ("rm -r %s", temp_dir);
57         if (system (command))
58             perror ("Failed to delete temp directory");
59     }
60
61     exit (status);
62 }
63
64 static void
65 fail (const gchar *event, const gchar *expected)
66 {
67     GList *link;
68
69     if (failed)
70         return;
71     failed = TRUE;
72
73     g_printerr ("Test failed, got the following events:\n");
74     for (link = statuses; link; link = link->next)
75         g_printerr ("    %s\n", (gchar *)link->data);
76     if (event)
77         g_printerr ("    %s\n", event);
78     if (expected)
79         g_printerr ("    ^^^ expected \"%s\"\n", expected);
80     else
81         g_printerr ("^^^ expected nothing\n");
82
83     /* Either wait for the daemon to quit, or stop now if it already is */
84     if (lightdm_pid)
85         stop_daemon ();
86     else
87         quit (EXIT_FAILURE);
88 }
89
90 static gchar *
91 get_script_line ()
92 {
93     if (!script_iter)
94         return NULL;
95     return script_iter->data;
96 }
97
98 static void
99 daemon_exit_cb (GPid pid, gint status, gpointer data)
100 {
101     gchar *status_text;
102
103     /* Quit when the daemon does */
104     if (failed)
105         quit (EXIT_FAILURE);
106
107     lightdm_pid = 0;
108   
109     if (WIFEXITED (status))
110         status_text = g_strdup_printf ("RUNNER DAEMON-EXIT STATUS=%d", WEXITSTATUS (status));
111     else
112         status_text = g_strdup_printf ("RUNNER DAEMON-TERMINATE SIGNAL=%d", WTERMSIG (status));
113     check_status (status_text);
114 }
115
116 static int
117 open_unix_socket (const gchar *name)
118 {
119     int s;
120     struct sockaddr_un address;
121
122     s = socket (AF_UNIX, SOCK_DGRAM, 0);
123     if (s < 0)
124         return -1;
125     address.sun_family = AF_UNIX;
126     strncpy (address.sun_path, name, UNIX_PATH_MAX);
127     if (bind (s, (struct sockaddr *) &address, sizeof (address)) < 0)
128         return -1;
129     return s;
130 }
131
132 static void
133 run_commands ()
134 {
135     /* Stop daemon if requested */
136     while (TRUE)
137     {
138         gchar *command, *name = NULL, *c;
139         GHashTable *params;
140
141         command = get_script_line ();
142         if (!command)
143             break;
144
145         /* Commands start with an asterisk */
146         if (command[0] != '*')
147             break;
148         statuses = g_list_append (statuses, g_strdup (command));
149         script_iter = script_iter->next;
150
151         c = command + 1;
152         while (*c && !isspace (*c))
153             c++;
154         name = g_strdup_printf ("%.*s", (int) (c - command - 1), command + 1);
155
156         params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
157         while (TRUE)
158         {
159             gchar *start, *param_name, *param_value;
160           
161             while (isspace (*c))
162                 c++;
163             start = c;
164             while (*c && !isspace (*c) && *c != '=')
165                 c++;
166             if (*c == '\0')
167                 break;
168
169             param_name = g_strdup_printf ("%.*s", (int) (c - start), start);
170
171             if (*c == '=')
172             {
173                 c++;
174                 while (isspace (*c))
175                     c++;
176                 if (*c == '\"')
177                 {
178                     gboolean escaped = FALSE;
179                     GString *value;
180
181                     c++;
182                     value = g_string_new ("");
183                     while (*c)
184                     {
185                         if (*c == '\\')
186                         {
187                             if (escaped)
188                             {
189                                 g_string_append_c (value, '\\');
190                                 escaped = FALSE;
191                             }
192                             else
193                                 escaped = TRUE;
194                         }
195                         else if (!escaped && *c == '\"')
196                             break;
197                         if (!escaped)
198                             g_string_append_c (value, *c);
199                         c++;
200                     }
201                     param_value = value->str;
202                     g_string_free (value, FALSE);
203                     if (*c == '\"')
204                         c++;
205                 }
206                 else
207                 {
208                     start = c;
209                     while (*c && !isspace (*c))
210                         c++;
211                     param_value = g_strdup_printf ("%.*s", (int) (c - start), start);
212                 }
213             }
214             else
215                 param_value = g_strdup ("");
216
217             g_hash_table_insert (params, param_name, param_value);
218         }
219
220         if (strcmp (name, "WAIT") == 0)
221         {
222             sleep (1);
223         }
224         else if (strcmp (name, "SWITCH-TO-GREETER") == 0)
225         {
226             g_dbus_connection_call_sync (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
227                                          "org.freedesktop.DisplayManager",
228                                          "/org/freedesktop/DisplayManager/Seat0",
229                                          "org.freedesktop.DisplayManager.Seat",
230                                          "SwitchToGreeter",
231                                          g_variant_new ("()"),
232                                          G_VARIANT_TYPE ("()"),
233                                          G_DBUS_CALL_FLAGS_NONE,
234                                          1000,
235                                          NULL,
236                                          NULL);
237             check_status ("RUNNER SWITCH-TO-GREETER");
238         }
239         else if (strcmp (name, "SWITCH-TO-USER") == 0)
240         {
241             gchar *status_text, *username;
242           
243             username = g_hash_table_lookup (params, "USERNAME");
244             g_dbus_connection_call_sync (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
245                                          "org.freedesktop.DisplayManager",
246                                          "/org/freedesktop/DisplayManager/Seat0",
247                                          "org.freedesktop.DisplayManager.Seat",
248                                          "SwitchToUser",
249                                          g_variant_new ("(ss)", username, ""),
250                                          G_VARIANT_TYPE ("()"),
251                                          G_DBUS_CALL_FLAGS_NONE,
252                                          1000,
253                                          NULL,
254                                          NULL);
255             status_text = g_strdup_printf ("RUNNER SWITCH-TO-USER USERNAME=%s", username);
256             check_status (status_text);
257             g_free (status_text);
258         }
259         else if (strcmp (name, "SWITCH-TO-GUEST") == 0)
260         {
261             g_dbus_connection_call_sync (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
262                                          "org.freedesktop.DisplayManager",
263                                          "/org/freedesktop/DisplayManager/Seat0",
264                                          "org.freedesktop.DisplayManager.Seat",
265                                          "SwitchToGuest",
266                                          g_variant_new ("(s)", ""),
267                                          G_VARIANT_TYPE ("()"),
268                                          G_DBUS_CALL_FLAGS_NONE,
269                                          1000,
270                                          NULL,
271                                          NULL);
272             check_status ("RUNNER SWITCH-TO-GUEST");
273         }
274         else if (strcmp (name, "STOP-DAEMON") == 0)
275         {
276             expect_exit = TRUE;
277             stop_daemon ();
278         }
279         else if (strcmp (name, "START-XSERVER") == 0)
280         {
281             gchar *xserver_args, *command_line;
282             gchar **argv;
283             GPid pid;
284             GError *error = NULL;
285
286             xserver_args = g_hash_table_lookup (params, "ARGS");
287             if (!xserver_args)
288                 xserver_args = "";
289             command_line = g_strdup_printf ("%s/tests/src/test-xserver %s", BUILDDIR, xserver_args);
290
291             g_debug ("Run %s", command_line);
292             if (!g_shell_parse_argv (command_line, NULL, &argv, &error) ||
293                 !g_spawn_async (NULL, argv, NULL, 0, NULL, NULL, &pid, &error))
294             {
295                 g_printerr ("Error starting X server: %s", error->message);
296                 quit (EXIT_FAILURE);
297                 return;
298             }
299             children = g_list_append (children, GINT_TO_POINTER (pid));
300         }
301         else
302         {
303             g_printerr ("Unknown command '%s'\n", name);
304             quit (EXIT_FAILURE);
305             return;
306         }
307
308         g_free (name);
309         g_hash_table_unref (params);
310     }
311
312     /* Stop at the end of the script */
313     if (get_script_line () == NULL)
314     {
315         if (lightdm_pid)
316         {
317             expect_exit = TRUE;
318             stop_daemon ();
319         }
320         else
321             quit (EXIT_SUCCESS);
322     }
323 }
324
325 static gboolean
326 status_timeout_cb (gpointer data)
327 {
328     fail ("(timeout)", get_script_line ());
329     return FALSE;
330 }
331
332 static void
333 check_status (const gchar *status)
334 {
335     gchar *pattern;
336
337     if (failed)
338         return;
339   
340     statuses = g_list_append (statuses, g_strdup (status));
341   
342     if (getenv ("DEBUG"))
343         g_print ("%s\n", status);
344
345     /* Try and match against expected */
346     pattern = get_script_line ();
347     if (!pattern || !g_regex_match_simple (pattern, status, 0, 0))
348     {
349         fail (NULL, pattern);
350         return;
351     }
352     script_iter = script_iter->next;
353
354     /* Restart timeout */
355     g_source_remove (status_timeout);
356     status_timeout = g_timeout_add (2000, status_timeout_cb, NULL);
357
358     run_commands ();
359 }
360
361 static gboolean
362 status_message_cb (GIOChannel *channel, GIOCondition condition, gpointer data)
363 {
364     int s;
365     guint8 buffer[1024];
366     ssize_t n_read;
367
368     s = g_io_channel_unix_get_fd (channel);
369     n_read = recv (s, buffer, 1023, 0);
370     if (n_read < 0)
371         g_warning ("Error reading from socket: %s", strerror (errno));
372     else if (n_read == 0)
373         return FALSE;
374     else
375     {
376         buffer[n_read] = '\0';
377         check_status ((gchar *) buffer);
378     }
379
380     return TRUE;
381 }
382
383 static void
384 signal_cb (int signum)
385 {
386     if (lightdm_pid != 0)
387     {
388         g_print ("Caught signal %d, killing daemon\n", signum);
389         stop_daemon ();
390     }
391     else
392     {
393         g_print ("Caught signal %d, quitting\n", signum);
394         quit (EXIT_FAILURE);
395     }
396 }
397
398 static void
399 load_script (const gchar *name)
400 {
401     int i;
402     gchar *filename, *path, *data, **lines;
403
404     filename = g_strdup_printf ("%s.script", name);
405     path = g_build_filename (SRCDIR, "tests", "scripts", filename, NULL);
406     g_free (filename);
407
408     if (!g_file_get_contents (path, &data, NULL, NULL))
409     {
410         g_printerr ("Unable to load script: %s\n", path);
411         quit (EXIT_FAILURE);
412     }
413     g_free (path);
414
415     lines = g_strsplit (data, "\n", -1);
416     g_free (data);
417
418     for (i = 0; lines[i]; i++)
419     {
420         gchar *line = g_strstrip (lines[i]);
421
422         /* Skip empty lines and comments */
423         if (line[0] == '\0' || line[0] == '#')
424             continue;
425
426         script = g_list_append (script, g_strdup (line));
427     }
428     script_iter = script;
429     g_strfreev (lines);
430 }
431
432 int
433 main (int argc, char **argv)
434 {
435     GMainLoop *loop;
436     gchar *greeter = NULL, *script_name, *config_file, *config_path, *path, *path1, *path2, *ld_library_path, *home_dir;
437     GString *passwd_data;
438     int status_socket;
439     gchar *dbus_command, dbus_address[1024];
440     GPid pid;
441     GString *command_line;
442     int dbus_pipe[2];
443     ssize_t n_read;
444     gchar **dbus_argv;
445     gchar **lightdm_argv;
446     gchar cwd[1024];
447     GError *error = NULL;
448
449     signal (SIGINT, signal_cb);
450     signal (SIGTERM, signal_cb);
451
452     g_type_init ();
453
454     loop = g_main_loop_new (NULL, FALSE);
455
456     if (argc != 2 && argc != 3)
457     {
458         g_printerr ("Usage %s SCRIPT-NAME [GREETER]\n", argv[0]);
459         quit (EXIT_FAILURE);
460     }
461     script_name = argv[1];
462     config_file = g_strdup_printf ("%s.conf", script_name);
463     config_path = g_build_filename (SRCDIR, "tests", "scripts", config_file, NULL);
464     g_free (config_file);
465
466     if (argc == 3)
467         greeter = argv[2];
468
469     load_script (script_name);
470     
471     g_print ("----------------------------------------\n");
472     g_print ("Running script %s\n", script_name);
473
474     if (!getcwd (cwd, 1024))
475     {
476         g_critical ("Error getting current directory: %s", strerror (errno));
477         quit (EXIT_FAILURE);
478     }
479   
480     /* Don't contact our X server */
481     g_unsetenv ("DISPLAY");
482
483     /* Use locally built libraries and binaries */
484     path = g_strdup_printf ("%s/tests/src/.libs:%s/tests/src:%s/tests/src:%s", BUILDDIR, BUILDDIR, SRCDIR, g_getenv ("PATH"));
485     g_setenv ("PATH", path, TRUE);
486     g_free (path);
487     path1 = g_build_filename (BUILDDIR, "liblightdm-gobject", ".libs", NULL);  
488     path2 = g_build_filename (BUILDDIR, "liblightdm-qt", ".libs", NULL);
489     ld_library_path = g_strdup_printf ("%s:%s", path1, path2);
490     g_free (path1);
491     g_free (path2);
492     g_setenv ("LD_LIBRARY_PATH", ld_library_path, TRUE);
493     g_free (ld_library_path);
494
495     /* Set config for child processes to read */
496     if (config_path)
497         g_setenv ("LIGHTDM_TEST_CONFIG", config_path, TRUE);
498
499     /* Run local D-Bus daemon */
500     if (pipe (dbus_pipe) < 0)
501     {
502         g_warning ("Error creating pipe: %s", strerror (errno));
503         quit (EXIT_FAILURE);
504     }
505     dbus_command = g_strdup_printf ("dbus-daemon --session --print-address=%d", dbus_pipe[1]);
506     if (!g_shell_parse_argv (dbus_command, NULL, &dbus_argv, &error))
507     {
508         g_warning ("Error parsing command line: %s", error->message);
509         quit (EXIT_FAILURE);
510     }
511     g_clear_error (&error);
512     if (!g_spawn_async (NULL, dbus_argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_LEAVE_DESCRIPTORS_OPEN, NULL, NULL, &pid, &error))
513     {
514         g_warning ("Error launching LightDM: %s", error->message);
515         quit (EXIT_FAILURE);
516     }
517     children = g_list_append (children, GINT_TO_POINTER (pid));
518     n_read = read (dbus_pipe[0], dbus_address, 1023);
519     if (n_read < 0)
520     {
521         g_warning ("Error reading D-Bus address: %s", strerror (errno));
522         quit (EXIT_FAILURE);
523     }
524     dbus_address[n_read] = '\0';
525     g_setenv ("DBUS_SESSION_BUS_ADDRESS", dbus_address, TRUE);
526
527     /* Open socket for status */
528     status_socket_name = g_build_filename (cwd, ".status-socket", NULL);
529     g_setenv ("LIGHTDM_TEST_STATUS_SOCKET", status_socket_name, TRUE);
530     unlink (status_socket_name);  
531     status_socket = open_unix_socket (status_socket_name);
532     if (status_socket < 0)
533     {
534         g_warning ("Error opening status socket: %s", strerror (errno));
535         quit (EXIT_FAILURE);
536     }
537     g_io_add_watch (g_io_channel_unix_new (status_socket), G_IO_IN, status_message_cb, NULL);
538
539     /* Make fake users */
540     temp_dir = g_build_filename (cwd, "lightdm-test-XXXXXX", NULL);
541     if (!mkdtemp (temp_dir))
542     {
543         g_warning ("Error creating temporary directory: %s", strerror (errno));
544         quit (EXIT_FAILURE);
545     }
546     home_dir = g_build_filename (temp_dir, "home", NULL);
547     g_setenv ("LIGHTDM_TEST_HOME_DIR", home_dir, TRUE);
548     passwd_data = g_string_new ("");
549
550     struct
551     {
552         gchar *user_name;
553         gchar *password;
554         gchar *real_name;
555         gint uid;
556     } users[] =
557     {
558         {"alice", "password", "Alice User", 1000},
559         {"bob", "", "Bob User", 1001},
560         {NULL, NULL, 0}
561     };
562     int i;
563     for (i = 0; users[i].user_name; i++)
564     {
565         path = g_build_filename (home_dir, users[i].user_name, NULL);
566         g_mkdir_with_parents (path, 0755);
567         g_free (path);
568
569         g_string_append_printf (passwd_data, "%s:%s:%d:%d:%s:%s/home/%s:/bin/sh\n", users[i].user_name, users[i].password, users[i].uid, users[i].uid, users[i].real_name, temp_dir, users[i].user_name);
570     }
571
572     path = g_build_filename (temp_dir, "passwd", NULL);
573     g_setenv ("LIGHTDM_TEST_PASSWD_FILE", path, TRUE);
574     g_file_set_contents (path, passwd_data->str, -1, NULL);
575     g_free (path);
576     g_string_free (passwd_data, TRUE);
577
578     run_commands ();
579   
580     status_timeout = g_timeout_add (2000, status_timeout_cb, NULL);
581
582     command_line = g_string_new ("../src/lightdm");
583     if (getenv ("DEBUG"))
584         g_string_append (command_line, " --debug");
585     if (fopen (config_path, "r"))
586         g_string_append_printf (command_line, " --config %s", config_path);
587     g_string_append (command_line, " --test-mode");
588     g_string_append(command_line, " --xserver-command=test-xserver");
589     if (greeter)
590         g_string_append_printf (command_line, " --greeter-session=%s", greeter);
591     g_string_append (command_line, " --session-wrapper=");
592     g_string_append_printf (command_line, " --passwd-file %s/passwd", temp_dir);
593     g_string_append_printf (command_line, " --cache-dir %s/cache", temp_dir);
594     g_string_append_printf (command_line, " --xsessions-dir=%s/tests/data/xsessions", SRCDIR);
595     g_string_append_printf (command_line, " --xgreeters-dir=%s/tests/data/xgreeters", SRCDIR);
596     g_string_append (command_line, " --minimum-vt=0");
597     g_string_append (command_line, " --minimum-display-number=50");
598
599     g_print ("Start daemon with command: PATH=%s LD_LIBRARY_PATH=%s LIGHTDM_TEST_STATUS_SOCKET=%s DBUS_SESSION_BUS_ADDRESS=%s %s\n",
600              g_getenv ("PATH"), g_getenv ("LD_LIBRARY_PATH"), g_getenv ("LIGHTDM_TEST_STATUS_SOCKET"), g_getenv ("DBUS_SESSION_BUS_ADDRESS"),
601              command_line->str);
602
603     if (!g_shell_parse_argv (command_line->str, NULL, &lightdm_argv, &error))
604     {
605         g_warning ("Error parsing command line: %s", error->message);
606         quit (EXIT_FAILURE);
607     }
608     g_clear_error (&error);
609
610     if (!g_spawn_async (NULL, lightdm_argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &lightdm_pid, &error))
611     {
612         g_warning ("Error launching LightDM: %s", error->message);
613         quit (EXIT_FAILURE);
614     }
615     g_clear_error (&error);
616
617     check_status ("RUNNER DAEMON-START");
618
619     g_child_watch_add (lightdm_pid, daemon_exit_cb, NULL);
620
621     g_main_loop_run (loop);
622
623     return EXIT_FAILURE;
624 }