[gnome-remote-desktop] tests: Add test for the RDP backend



commit 1994d91495ea729ce9f952611b4a6abd51e90d56
Author: Pascal Nowack <Pascal Nowack gmx de>
Date:   Sun Feb 13 13:48:53 2022 +0100

    tests: Add test for the RDP backend
    
    This test will verify whether the server correctly created a virtual
    monitor with the wanted size of the client.
    The test is passed, as soon as the first frame update of the virtual
    monitor is received with the correct resolution.

 .gitlab-ci.yml           |   3 +-
 .gitlab-ci/run-tests.sh  |   9 +
 tests/meson.build        |  25 +++
 tests/rdp-test-runner.sh |   5 +
 tests/run-rdp-tests.py   | 101 +++++++++++
 tests/test-client-rdp.c  | 434 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 576 insertions(+), 1 deletion(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fb88b988..5d4598c6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,7 +9,7 @@ stages:
 .gnome-remote-desktop.fedora:35@common:
   variables:
     FDO_DISTRIBUTION_VERSION: 35
-    BASE_TAG: '2022-05-04.0'
+    BASE_TAG: '2022-05-05.0'
     FDO_UPSTREAM_REPO: GNOME/gnome-remote-desktop
     FDO_DISTRIBUTION_EXEC: |
       dnf -y update && dnf -y upgrade &&
@@ -24,6 +24,7 @@ stages:
       dnf install -y asciidoc &&
 
       # To test
+      dnf install -y openssl &&
       dnf install -y 'pkgconfig(libvncclient)' &&
       dnf remove -y pipewire0.2-devel pipewire0.2-libs &&
       dnf install -y 'pkgconfig(libpipewire-0.3)' &&
diff --git a/.gitlab-ci/run-tests.sh b/.gitlab-ci/run-tests.sh
index 5f7aea4a..3d5afe18 100755
--- a/.gitlab-ci/run-tests.sh
+++ b/.gitlab-ci/run-tests.sh
@@ -7,5 +7,14 @@ sleep 1
 wireplumber &
 sleep 1
 
+openssl req -new -newkey rsa:4096 -days 720 -nodes -x509 \
+            -subj "/C=DE/ST=NONE/L=NONE/O=GNOME/CN=gnome.org" \
+            -keyout tls.key -out tls.crt
+gsettings set org.gnome.desktop.remote-desktop.rdp tls-cert $(realpath tls.crt)
+gsettings set org.gnome.desktop.remote-desktop.rdp tls-key $(realpath tls.key)
+gsettings set org.gnome.desktop.remote-desktop.rdp screen-share-mode extend
+gsettings set org.gnome.desktop.remote-desktop.rdp enable true
+
 gsettings set org.gnome.desktop.remote-desktop.vnc enable true
+
 meson test -C build --no-rebuild --verbose --no-stdsplit -t 10
diff --git a/tests/meson.build b/tests/meson.build
index 6f87c458..2148a353 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,3 +1,28 @@
+if have_rdp
+  test_client_rdp = executable(
+    'test-client-rdp',
+    files(['test-client-rdp.c']),
+    dependencies: [glib_dep,
+                   freerdp_dep,
+                   freerdp_client_dep,
+                   winpr_dep],
+    include_directories: [configinc],
+    install: false)
+
+  test_runner = find_program('rdp-test-runner.sh')
+
+  test_env = environment()
+  test_env.set('TEST_SRCDIR', top_srcdir)
+  test_env.set('TEST_BUILDDIR', builddir)
+  test_env.set('NO_AT_BRIDGE', '1')
+
+  test('gnome-remote-desktop/rdp', test_runner,
+    env: test_env,
+    is_parallel: false,
+    timeout: 10,
+  )
+endif
+
 if have_vnc
   test_client_vnc = executable(
     'test-client-vnc',
diff --git a/tests/rdp-test-runner.sh b/tests/rdp-test-runner.sh
new file mode 100755
index 00000000..7b6664dd
--- /dev/null
+++ b/tests/rdp-test-runner.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+export WLOG_LEVEL=DEBUG
+export G_MESSAGES_DEBUG=all
+dbus-run-session -- $TEST_SRCDIR/tests/run-rdp-tests.py
diff --git a/tests/run-rdp-tests.py b/tests/run-rdp-tests.py
new file mode 100755
index 00000000..66a5fc5c
--- /dev/null
+++ b/tests/run-rdp-tests.py
@@ -0,0 +1,101 @@
+#!/usr/bin/python3
+
+import dbus
+import os
+import time
+import os.path
+import subprocess
+import sys
+
+from gi.repository import GLib
+
+from dbus.mainloop.glib import DBusGMainLoop
+
+mutter = None
+
+DBusGMainLoop(set_as_default=True)
+loop = GLib.MainLoop()
+bus = dbus.SessionBus()
+
+rdp_client_failed = None
+rdp_server_failed = None
+
+os.environ['GNOME_REMOTE_DESKTOP_TEST_RDP_USERNAME'] = 'TestU'
+os.environ['GNOME_REMOTE_DESKTOP_TEST_RDP_PASSWORD'] = 'TestPw'
+
+def run_rdp_test_client():
+  print("Running RDP test client")
+  global rdp_client_failed
+  builddir=os.getenv("TEST_BUILDDIR")
+  rdp_test_client_path = os.path.join(builddir, 'tests', 'test-client-rdp')
+  rdp_test_client = subprocess.Popen([rdp_test_client_path, '/v:127.0.0.1:3395', '/u:TestU', '/p:TestPw'],
+                                     stderr=subprocess.STDOUT)
+  rdp_test_client.wait()
+  if rdp_test_client.wait() != 0:
+    print("RDP test client exited incorrectly")
+    rdp_client_failed = True
+  else:
+    rdp_client_failed = False
+
+def start_rdp_server():
+  print("Starting RDP server")
+  global rdp_server
+  builddir=os.getenv("TEST_BUILDDIR")
+  rdp_server_path = os.path.join(builddir, 'src', 'gnome-remote-desktop-daemon')
+  rdp_server = subprocess.Popen([rdp_server_path, '--rdp-port', '3395'],
+                                stderr=subprocess.STDOUT)
+  time.sleep(5)
+
+def stop_rdp_server():
+  print("Stopping RDP server")
+  global rdp_server
+  global rdp_server_failed
+  rdp_server.terminate()
+  rdp_server.wait()
+  if rdp_server.returncode != -15 and rdp_server.returncode != 0:
+    print("RDP server exited incorrectly: %d"%(rdp_server.returncode))
+    rdp_server_failed = True
+  else:
+    rdp_server_failed = False
+
+def remote_desktop_name_appeared_cb(name):
+  if name == '':
+    return
+  print("Remote desktop capable display server appeared")
+  start_rdp_server()
+  run_rdp_test_client()
+  stop_rdp_server()
+  stop_mutter()
+
+def start_mutter():
+  global mutter
+  print("Starting mutter")
+  mutter = subprocess.Popen(['mutter', '--headless', '--wayland', '--no-x11'],
+                            stderr=subprocess.STDOUT)
+
+def stop_mutter():
+  global mutter
+  global loop
+  if mutter == None:
+    print("no mutter")
+    return
+  print("Stopping mutter")
+  mutter.terminate()
+  print("Waiting for mutter to terminate")
+  if mutter.wait() != 0:
+    print("Mutter exited incorrectly")
+    sys.exit(1)
+  print("Done")
+  loop.quit()
+
+bus.watch_name_owner('org.gnome.Mutter.RemoteDesktop',
+                     remote_desktop_name_appeared_cb)
+
+start_mutter()
+
+loop.run()
+
+if rdp_server_failed != False or rdp_client_failed != False:
+  sys.exit(1)
+else:
+  sys.exit(0)
diff --git a/tests/test-client-rdp.c b/tests/test-client-rdp.c
new file mode 100644
index 00000000..8c9c9602
--- /dev/null
+++ b/tests/test-client-rdp.c
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2022 Pascal Nowack
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <freerdp/client/cmdline.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gfx.h>
+#include <glib.h>
+
+#define TEST_1_WIDTH 800
+#define TEST_1_HEIGHT 600
+
+typedef enum _TestState
+{
+  TEST_STATE_TEST_1,
+  TEST_STATE_TESTS_COMPLETE,
+} TestState;
+
+typedef struct _RdpPeerContext
+{
+  rdpContext rdp_context;
+
+  HANDLE stop_event;
+
+  TestState test_state;
+} RdpPeerContext;
+
+static const char *
+test_state_to_test_string (TestState test_state)
+{
+  switch (test_state)
+    {
+    case TEST_STATE_TEST_1:
+      return "1 (Virtual monitor size)";
+    case TEST_STATE_TESTS_COMPLETE:
+      g_assert_not_reached ();
+    }
+
+  g_assert_not_reached ();
+}
+
+static void
+transition_test_state (RdpPeerContext *rdp_peer_context)
+{
+  g_message ("Test %s was successful!",
+             test_state_to_test_string (rdp_peer_context->test_state));
+
+  switch (rdp_peer_context->test_state)
+    {
+    case TEST_STATE_TEST_1:
+      rdp_peer_context->test_state = TEST_STATE_TESTS_COMPLETE;
+      break;
+    case TEST_STATE_TESTS_COMPLETE:
+      break;
+    }
+
+  if (rdp_peer_context->test_state == TEST_STATE_TESTS_COMPLETE)
+    freerdp_client_stop (&rdp_peer_context->rdp_context);
+}
+
+static void
+exit_test_failure (RdpPeerContext *rdp_peer_context)
+{
+  g_warning ("Test %s failed!",
+             test_state_to_test_string (rdp_peer_context->test_state));
+  freerdp_client_stop (&rdp_peer_context->rdp_context);
+}
+
+static void
+handle_test_result (RdpPeerContext *rdp_peer_context,
+                    gboolean        success)
+{
+  if (success)
+    transition_test_state (rdp_peer_context);
+  else
+    exit_test_failure (rdp_peer_context);
+}
+
+static void
+init_graphics_pipeline (RdpPeerContext      *rdp_peer_context,
+                        RdpgfxClientContext *rdpgfx_context)
+{
+  rdpContext *rdp_context = &rdp_peer_context->rdp_context;
+
+  gdi_graphics_pipeline_init (rdp_context->gdi, rdpgfx_context);
+}
+
+static void
+uninit_graphics_pipeline (RdpPeerContext      *rdp_peer_context,
+                          RdpgfxClientContext *rdpgfx_context)
+{
+  rdpContext *rdp_context = &rdp_peer_context->rdp_context;
+
+  gdi_graphics_pipeline_uninit (rdp_context->gdi, rdpgfx_context);
+}
+
+static void
+on_channel_connected (void                      *user_data,
+                      ChannelConnectedEventArgs *event_args)
+{
+  if (strcmp (event_args->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+    init_graphics_pipeline (user_data, event_args->pInterface);
+}
+
+static void
+on_channel_disconnected (void                         *user_data,
+                         ChannelDisconnectedEventArgs *event_args)
+{
+  if (strcmp (event_args->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+    uninit_graphics_pipeline (user_data, event_args->pInterface);
+}
+
+static BOOL
+rdp_client_pre_connect (freerdp *instance)
+{
+  rdpContext *rdp_context = instance->context;
+  rdpSettings *rdp_settings = rdp_context->settings;
+  rdpChannels *rdp_channels = rdp_context->channels;
+
+  rdp_settings->OsMajorType = OSMAJORTYPE_UNIX;
+  rdp_settings->OsMinorType = OSMINORTYPE_PSEUDO_XSERVER;
+  rdp_settings->AsyncInput = FALSE;
+  rdp_settings->NetworkAutoDetect = TRUE;
+  rdp_settings->RedirectClipboard = FALSE;
+  rdp_settings->SupportGraphicsPipeline = TRUE;
+  rdp_settings->SupportDisplayControl = FALSE;
+
+  PubSub_SubscribeChannelConnected (rdp_context->pubSub,
+                                    on_channel_connected);
+  PubSub_SubscribeChannelDisconnected (rdp_context->pubSub,
+                                       on_channel_disconnected);
+
+  if (!freerdp_client_load_addins (rdp_channels, instance->settings))
+    return FALSE;
+
+  rdp_settings->DesktopWidth = TEST_1_WIDTH;
+  rdp_settings->DesktopHeight = TEST_1_HEIGHT;
+
+  return TRUE;
+}
+
+static BOOL
+rdp_update_desktop_resize (rdpContext *rdp_context)
+{
+  RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context;
+  rdpSettings *rdp_settings = rdp_context->settings;
+
+  g_message ("New desktop size: [%u, %u]",
+             rdp_settings->DesktopWidth, rdp_settings->DesktopHeight);
+
+  if (!gdi_resize (rdp_context->gdi,
+                   rdp_settings->DesktopWidth,
+                   rdp_settings->DesktopHeight))
+    return FALSE;
+
+  if (rdp_peer_context->test_state == TEST_STATE_TEST_1 &&
+      (rdp_settings->DesktopWidth != TEST_1_WIDTH ||
+       rdp_settings->DesktopHeight != TEST_1_HEIGHT))
+    exit_test_failure (rdp_peer_context);
+
+  return TRUE;
+}
+
+static BOOL
+rdp_update_end_paint (rdpContext *rdp_context)
+{
+  RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context;
+  int32_t x, y;
+  uint32_t width, height;
+
+  x = rdp_context->gdi->primary->hdc->hwnd->invalid->x;
+  y = rdp_context->gdi->primary->hdc->hwnd->invalid->y;
+  width = rdp_context->gdi->primary->hdc->hwnd->invalid->w;
+  height = rdp_context->gdi->primary->hdc->hwnd->invalid->h;
+
+  g_message ("Region invalidated: [x: %i, y: %i, w: %u, h: %u]",
+             x, y, width, height);
+
+  if (rdp_peer_context->test_state == TEST_STATE_TEST_1)
+    {
+      gboolean success;
+
+      success = x == 0 &&
+                y == 0 &&
+                width == TEST_1_WIDTH &&
+                height == TEST_1_HEIGHT;
+
+      handle_test_result (rdp_peer_context, success);
+    }
+
+  return TRUE;
+}
+
+static BOOL
+rdp_client_post_connect (freerdp *instance)
+{
+  if (!gdi_init (instance, PIXEL_FORMAT_BGRX32))
+    return FALSE;
+
+  instance->context->update->DesktopResize = rdp_update_desktop_resize;
+  instance->context->update->EndPaint = rdp_update_end_paint;
+
+  return TRUE;
+}
+
+static void
+rdp_client_post_disconnect (freerdp *instance)
+{
+  rdpContext *rdp_context = instance->context;
+
+  gdi_free (instance);
+
+  PubSub_UnsubscribeChannelDisconnected (rdp_context->pubSub,
+                                         on_channel_disconnected);
+  PubSub_UnsubscribeChannelConnected (rdp_context->pubSub,
+                                      on_channel_connected);
+}
+
+static BOOL
+rdp_client_authenticate (freerdp  *instance,
+                         char    **username,
+                         char    **password,
+                         char    **domain)
+{
+  /* Credentials were already parsed from the command line */
+
+  return TRUE;
+}
+
+static uint32_t
+rdp_client_verify_certificate_ex (freerdp    *instance,
+                                  const char *host,
+                                  uint16_t    port,
+                                  const char *common_name,
+                                  const char *subject,
+                                  const char *issuer,
+                                  const char *fingerprint,
+                                  uint32_t    flags)
+{
+  /* For this test client, always accept any certificate */
+
+  return 2;
+}
+
+static uint32_t
+rdp_client_verify_changed_certificate_ex (freerdp    *instance,
+                                          const char *host,
+                                          uint16_t    port,
+                                          const char *common_name,
+                                          const char *subject,
+                                          const char *issuer,
+                                          const char *fingerprint,
+                                          const char *old_subject,
+                                          const char *old_issuer,
+                                          const char *old_fingerprint,
+                                          uint32_t    flags)
+{
+  return 2;
+}
+
+static void
+on_terminate (void               *user_data,
+              TerminateEventArgs *event_args)
+{
+  rdpContext *rdp_context = user_data;
+
+  freerdp_abort_connect (rdp_context->instance);
+}
+
+static BOOL
+rdp_client_new (freerdp    *instance,
+                rdpContext *rdp_context)
+{
+  RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context;
+
+  g_message ("Creating client");
+
+  instance->PreConnect = rdp_client_pre_connect;
+  instance->PostConnect = rdp_client_post_connect;
+  instance->PostDisconnect = rdp_client_post_disconnect;
+  instance->Authenticate = rdp_client_authenticate;
+  instance->VerifyCertificateEx = rdp_client_verify_certificate_ex;
+  instance->VerifyChangedCertificateEx = rdp_client_verify_changed_certificate_ex;
+
+  PubSub_SubscribeTerminate (rdp_context->pubSub, on_terminate);
+
+  rdp_peer_context->stop_event = CreateEvent (NULL, TRUE, FALSE, NULL);
+
+  return TRUE;
+}
+
+static void
+rdp_client_free (freerdp    *instance,
+                 rdpContext *rdp_context)
+{
+  RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context;
+
+  g_message ("Freeing client");
+
+  g_clear_pointer (&rdp_peer_context->stop_event, CloseHandle);
+
+  PubSub_UnsubscribeTerminate (rdp_context->pubSub, on_terminate);
+}
+
+static int
+rdp_client_start (rdpContext *rdp_context)
+{
+  if (!rdp_context->settings->ServerHostname)
+    return -1;
+
+  return 0;
+}
+
+static int
+rdp_client_stop (rdpContext *rdp_context)
+{
+  RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context;
+
+  SetEvent (rdp_peer_context->stop_event);
+
+  return 0;
+}
+
+static gboolean
+run_main_loop (rdpContext *rdp_context)
+{
+  RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context;
+  HANDLE events[64];
+  uint32_t n_events;
+  uint32_t n_freerdp_handles;
+
+  rdp_peer_context->test_state = TEST_STATE_TEST_1;
+
+  if (!freerdp_connect (rdp_context->instance))
+    {
+      g_warning ("Failed to connect to server");
+      return FALSE;
+    }
+
+  while (!freerdp_shall_disconnect (rdp_context->instance))
+    {
+      n_events = 0;
+
+      events[n_events++] = rdp_peer_context->stop_event;
+
+      n_freerdp_handles = freerdp_get_event_handles (rdp_context,
+                                                     &events[n_events],
+                                                     64 - n_events);
+      if (!n_freerdp_handles)
+        {
+          g_warning ("Failed to get FreeRDP event handles");
+          return FALSE;
+        }
+      n_events += n_freerdp_handles;
+
+      WaitForMultipleObjects (n_events, events, FALSE, INFINITE);
+
+      if (WaitForSingleObject (rdp_peer_context->stop_event, 0) == WAIT_OBJECT_0)
+        break;
+
+      if (!freerdp_check_event_handles (rdp_context))
+        {
+          if (client_auto_reconnect_ex (rdp_context->instance, NULL))
+            continue;
+
+          if (freerdp_get_last_error (rdp_context) == FREERDP_ERROR_SUCCESS)
+            {
+              g_warning ("Failed to check FreeRDP file descriptor");
+              break;
+            }
+        }
+    }
+
+  return rdp_peer_context->test_state == TEST_STATE_TESTS_COMPLETE;
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+  RDP_CLIENT_ENTRY_POINTS client_entry_points = {};
+  rdpContext *rdp_context;
+  gboolean success;
+
+  client_entry_points.Version = 1;
+  client_entry_points.Size = sizeof (RDP_CLIENT_ENTRY_POINTS_V1);
+  client_entry_points.ContextSize = sizeof (RdpPeerContext);
+  client_entry_points.ClientNew = rdp_client_new;
+  client_entry_points.ClientFree = rdp_client_free;
+  client_entry_points.ClientStart = rdp_client_start;
+  client_entry_points.ClientStop = rdp_client_stop;
+
+  rdp_context = freerdp_client_context_new (&client_entry_points);
+  if (!rdp_context)
+    g_error ("Failed to create client context");
+
+  if (freerdp_client_settings_parse_command_line (rdp_context->settings,
+                                                  argc, argv, FALSE))
+    {
+      g_warning ("Failed to parse command line");
+      freerdp_client_context_free (rdp_context);
+      return 1;
+    }
+  if (freerdp_client_start (rdp_context))
+    {
+      g_warning ("Failed to start client");
+      freerdp_client_context_free (rdp_context);
+      return 1;
+    }
+
+  success = run_main_loop (rdp_context);
+
+  freerdp_client_context_free (rdp_context);
+
+  if (success)
+    return 0;
+
+  return 1;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]