[grilo] net: Support mocking of network answers



commit 7b168fcff60cdaf99f5967d0754777793c64bbcc
Author: Jens Georg <jensg openismus com>
Date:   Thu Oct 11 15:09:09 2012 +0200

    net: Support mocking of network answers
    
    Mock network answers of webservices through predefined files. This is useful
    for offline testing of plug-ins that provide sources from webservices. See the
    header of libs/net/grl-net-mock.c for full documentation.

 libs/net/Makefile.am             |    3 +-
 libs/net/grl-net-mock.c          |  262 ++++++++++++++++++++++++++++++++++++++
 libs/net/grl-net-mock.h          |   49 +++++++
 libs/net/grl-net-private.c       |   43 ++++++
 libs/net/grl-net-private.h       |    4 +
 libs/net/grl-net-soup-stable.c   |    6 +-
 libs/net/grl-net-soup-unstable.c |    5 +
 libs/net/grl-net-wc.c            |   24 +++-
 8 files changed, 390 insertions(+), 6 deletions(-)
---
diff --git a/libs/net/Makefile.am b/libs/net/Makefile.am
index c6d8fb3..fd05d58 100644
--- a/libs/net/Makefile.am
+++ b/libs/net/Makefile.am
@@ -13,7 +13,8 @@ libgrlnet_ GRL_MAJORMINOR@_la_DEPENDENCIES =	\
 
 libgrlnet_ GRL_MAJORMINOR@_la_SOURCES = \
 	grl-net-private.c		\
-	grl-net-wc.c
+	grl-net-wc.c		\
+	grl-net-mock.c
 
 libgrlnet_ GRL_MAJORMINOR@_la_CFLAGS =	\
 	-I $(top_srcdir)/src		\
diff --git a/libs/net/grl-net-mock.c b/libs/net/grl-net-mock.c
new file mode 100644
index 0000000..2bc21ae
--- /dev/null
+++ b/libs/net/grl-net-mock.c
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2012 Openismus GmbH
+ *
+ * Authors: Jens Georg <jensg openismus com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/*
+ * Mock network answers of webservices through predefined files. Useful for
+ * offline testing of plug-ins that provide sources from webservices.
+ *
+ * For configuring mock answers, a simple .ini file is used. The file is split
+ * into a "default" section and one section per URL.
+ * [default]
+ * version = 1
+ * ignore-parameters = [true,false]
+ *
+ * [http://www.example.com]
+ * data = content/of/response.txt
+ * timeout = 500
+ *
+ * Explanation of [default] parameters
+ * version needs to be "1"
+ * ignore-parameters can be used to map urls to sections without paying
+ * attention to query parameters, so that http://www.example.com?q=test+query
+ * will also match http://www.example.com . Default for this parameter is
+ * "false".
+ *
+ * Explanation of [url] sections
+ * The section title is used to map urls to response files.
+ * "data" is a path to a text file containing the raw response of the websserver.
+ * The path may be relative to the configuration file or an absolute path.
+ * "timeout" may be used to delay the response and in seconds. The default is
+ * don't delay at all.
+ * If you want to provoke a "not found" error, skip the "data" parameter.
+ *
+ * The name of the configuration file is either "grl-mock-data.ini" which is
+ * expected to be in the current directory or can be overridden by setting the
+ * environment variable GRL_REQUEST_MOCK_FILE.
+ *
+ * An easy way to capture the responses is to run your application with the
+ * environment variable GRL_WEB_CAPTURE_DIR. GrlNetWc will then write all
+ * each response into a file following the pattern "<url>-timestamp". If the
+ * directory does not exist yet it will be created.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <libsoup/soup.h>
+
+#define _GRILO_H_INSIDE_
+#include <grl-log.h>
+
+#include "grl-net-mock.h"
+
+#define GRL_MOCK_VERSION 1
+
+static GKeyFile *config;
+gboolean ignore_parameters;
+static char *base_path;
+
+void
+get_url_mocked (GrlNetWc *self,
+                const char *url,
+                GHashTable *headers,
+                GAsyncResult *result,
+                GCancellable *cancellable)
+{
+  char *data_file, *full_path;
+  GError *error = NULL;
+  GStatBuf stat_buf;
+  char *new_url;
+
+  if (ignore_parameters) {
+    SoupURI *uri = soup_uri_new (url);
+    soup_uri_set_query (uri, NULL);
+    new_url = soup_uri_to_string (uri, FALSE);
+    soup_uri_free (uri);
+  } else {
+    new_url = g_strdup (url);
+  }
+
+  if (!config) {
+    g_simple_async_result_set_error (G_SIMPLE_ASYNC_RESULT (result),
+                                     GRL_NET_WC_ERROR,
+                                     GRL_NET_WC_ERROR_NETWORK_ERROR,
+                                     "%s",
+                                     "No mock definition found");
+    g_simple_async_result_complete_in_idle (G_SIMPLE_ASYNC_RESULT (result));
+    return;
+  }
+
+  data_file = g_key_file_get_value (config, new_url, "data", &error);
+  if (error) {
+    g_simple_async_result_set_error (G_SIMPLE_ASYNC_RESULT (result),
+                                     GRL_NET_WC_ERROR,
+                                     GRL_NET_WC_ERROR_NOT_FOUND,
+                                     "Could not find mock content: %s",
+                                     error->message);
+    g_error_free (error);
+    g_simple_async_result_complete_in_idle (G_SIMPLE_ASYNC_RESULT (result));
+    return;
+  }
+  if (data_file[0] != '/') {
+    full_path = g_build_filename (base_path, data_file, NULL);
+  } else {
+    full_path = data_file;
+    data_file = NULL;
+  }
+
+  if (g_stat (full_path, &stat_buf) < 0) {
+    g_simple_async_result_set_error (G_SIMPLE_ASYNC_RESULT (result),
+                                     GRL_NET_WC_ERROR,
+                                     GRL_NET_WC_ERROR_NOT_FOUND,
+                                     "%s",
+                                     "Could not access mock content");
+    g_simple_async_result_complete_in_idle (G_SIMPLE_ASYNC_RESULT (result));
+    if (data_file)
+      g_free (data_file);
+    if (full_path)
+      g_free (full_path);
+    return;
+  }
+  if (data_file)
+    g_free (data_file);
+  if (full_path)
+    g_free (full_path);
+
+  g_simple_async_result_set_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result),
+                                             new_url,
+                                             NULL);
+  g_simple_async_result_complete_in_idle (G_SIMPLE_ASYNC_RESULT (result));
+}
+
+void
+get_content_mocked (GrlNetWc *self,
+                    void *op,
+                    gchar **content,
+                    gsize *length)
+{
+  char *url = (char *) op;
+  char *data_file = NULL, *full_path = NULL;
+  GError *error = NULL;
+
+  data_file = g_key_file_get_value (config, url, "data", NULL);
+  if (data_file[0] != '/') {
+    full_path = g_build_filename (base_path, data_file, NULL);
+  } else {
+    full_path = data_file;
+    data_file = NULL;
+  }
+  g_file_get_contents (full_path, content, length, &error);
+
+  if (data_file)
+    g_free (data_file);
+
+  if (full_path)
+    g_free (full_path);
+}
+
+void init_mock_requester (GrlNetWc *self)
+{
+  const char *env;
+  GError *error = NULL;
+  int version;
+  GFile *file, *parent;
+
+  base_path = NULL;
+
+  config = g_key_file_new ();
+
+  env = g_getenv ("GRL_REQUEST_MOCK_FILE");
+  if (env) {
+    GRL_DEBUG ("Trying to load mock file %s", env);
+    g_key_file_load_from_file (config,
+                               env,
+                               G_KEY_FILE_NONE,
+                               &error);
+  }
+  if (error) {
+    GRL_WARNING ("Failed to load mock file %s: %s", env, error->message);
+    g_error_free (error);
+    error = NULL;
+  }
+
+  /* Check if we managed to load a file */
+  version = g_key_file_get_integer (config, "default", "version", &error);
+  if (error || version < GRL_MOCK_VERSION) {
+    if (error) {
+      g_error_free (error);
+      error = NULL;
+    } else {
+      GRL_WARNING ("Unsupported mock version %d, trying default file.", version);
+    }
+
+    env = "grl-mock-data.ini";
+
+    g_key_file_load_from_file (config,
+                               env,
+                               G_KEY_FILE_NONE,
+                               &error);
+    if (error) {
+      GRL_WARNING ("Failed to load default mock file: %s", error->message);
+      g_error_free (error);
+
+      g_key_file_unref (config);
+      config = NULL;
+    }
+  }
+
+  if (!config) {
+    return;
+  }
+
+  ignore_parameters = g_key_file_get_boolean (config, "default", "ignore-parameters", &error);
+  if (error) {
+    ignore_parameters = FALSE;
+    g_error_free (error);
+  }
+
+  file = g_file_new_for_commandline_arg (env);
+  parent = g_file_get_parent (file);
+  g_object_unref (file);
+
+  base_path = g_file_get_path (parent);
+  g_object_unref (parent);
+}
+
+void finalize_mock_requester (GrlNetWc *self)
+{
+  if (config) {
+    g_key_file_unref (config);
+  }
+
+  if (base_path) {
+    g_free (base_path);
+  }
+}
+
+void free_mock_op_res (void *op)
+{
+  g_free (op);
+}
diff --git a/libs/net/grl-net-mock.h b/libs/net/grl-net-mock.h
new file mode 100644
index 0000000..3b57fc6
--- /dev/null
+++ b/libs/net/grl-net-mock.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 Openismus GmbH
+ *
+ * Authors: Jens Georg <jensg openismus com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_NET_MOCK_H_
+#define _GRL_NET_MOCK_H_
+
+#include "grl-net-wc.h"
+
+#define GRL_ENV_NET_MOCKED "GRL_NET_MOCKED"
+
+#define GRL_NET_IS_MOCKED (g_getenv (GRL_ENV_NET_MOCKED))
+
+void get_url_mocked (GrlNetWc *self,
+                     const char *url,
+                     GHashTable *headers,
+                     GAsyncResult *result,
+                     GCancellable *cancellable) G_GNUC_INTERNAL;
+
+void get_content_mocked (GrlNetWc *self,
+                         void *op,
+                         gchar **content,
+                         gsize *length) G_GNUC_INTERNAL;
+
+void init_mock_requester (GrlNetWc *self) G_GNUC_INTERNAL;
+
+void finalize_mock_requester (GrlNetWc *self) G_GNUC_INTERNAL;
+
+void free_mock_op_res (void *op) G_GNUC_INTERNAL;
+
+#endif /* _GRL_NET_MOCK_H_ */
diff --git a/libs/net/grl-net-private.c b/libs/net/grl-net-private.c
index 7c1e557..401c913 100644
--- a/libs/net/grl-net-private.c
+++ b/libs/net/grl-net-private.c
@@ -89,3 +89,46 @@ parse_error (guint status,
     g_message ("Unhandled status: %s", soup_status_get_phrase (status));
   }
 }
+
+void
+init_dump_directory ()
+{
+  const char *path;
+
+  path = g_getenv ("GRL_WEB_CAPTURE_DIR");
+  if (!path)
+    return;
+
+  g_mkdir_with_parents (path, 0700);
+}
+
+void
+dump_data (SoupURI *soup_uri,
+           const char *buffer,
+           const gsize length)
+{
+  const char *capture_dir;
+  char *uri, *escaped_uri, *file;
+  GError *error = NULL;
+
+  capture_dir = g_getenv ("GRL_WEB_CAPTURE_DIR");
+
+  if (!capture_dir)
+    return;
+
+  uri = soup_uri_to_string (soup_uri, FALSE);
+  escaped_uri = g_uri_escape_string (uri, NULL, FALSE);
+  g_free (uri);
+  file = g_strdup_printf ("%s"G_DIR_SEPARATOR_S"%s-%"G_GINT64_FORMAT,
+                          capture_dir,
+                          escaped_uri,
+                          g_get_real_time ());
+  g_free (escaped_uri);
+
+  if (!g_file_set_contents (file, buffer, length, &error)) {
+    GRL_WARNING ("Could not write contents to disk: %s", error->message);
+    g_error_free (error);
+  }
+
+  g_free (file);
+}
diff --git a/libs/net/grl-net-private.h b/libs/net/grl-net-private.h
index 3d15a0c..f0ddc65 100644
--- a/libs/net/grl-net-private.h
+++ b/libs/net/grl-net-private.h
@@ -76,6 +76,10 @@ guint cache_get_size (GrlNetWc *self);
 
 void free_op_res (void *op);
 
+void init_dump_directory (void) G_GNUC_INTERNAL;
+
+void dump_data (SoupURI *soup_uri, const gchar *data, gsize length) G_GNUC_INTERNAL;
+
 G_END_DECLS
 
 #endif /* _GRL_NET_PRIVATE_H_ */
diff --git a/libs/net/grl-net-soup-stable.c b/libs/net/grl-net-soup-stable.c
index 70ae7ec..cdd615d 100644
--- a/libs/net/grl-net-soup-stable.c
+++ b/libs/net/grl-net-soup-stable.c
@@ -147,12 +147,16 @@ get_content (GrlNetWc *self,
 
   if (length)
     *length = (gsize) msg->response_body->length;
+
+  dump_data (soup_message_get_uri (msg),
+             msg->response_body->data,
+             msg->response_body->length);
 }
 
 void
 init_requester (GrlNetWc *self)
 {
-  /* noop */
+  init_dump_directory ();
 }
 
 void
diff --git a/libs/net/grl-net-soup-unstable.c b/libs/net/grl-net-soup-unstable.c
index cc81c38..2178f98 100644
--- a/libs/net/grl-net-soup-unstable.c
+++ b/libs/net/grl-net-soup-unstable.c
@@ -52,6 +52,7 @@ init_requester (GrlNetWc *self)
   priv->requester = soup_requester_new ();
   soup_session_add_feature (priv->session,
                             SOUP_SESSION_FEATURE (priv->requester));
+  init_dump_directory ();
 }
 
 void
@@ -287,6 +288,10 @@ get_content (GrlNetWc *self,
   GrlNetWcPrivate *priv = self->priv;
   struct request_res *rr = op;
 
+  dump_data (soup_request_get_uri (rr->request),
+             rr->buffer,
+             rr->offset);
+
   if (priv->previous_data)
     g_free (priv->previous_data);
 
diff --git a/libs/net/grl-net-wc.c b/libs/net/grl-net-wc.c
index 85efe6d..4683b76 100644
--- a/libs/net/grl-net-wc.c
+++ b/libs/net/grl-net-wc.c
@@ -44,6 +44,7 @@
 #include <grilo.h>
 #include "grl-net-wc.h"
 #include "grl-net-private.h"
+#include "grl-net-mock.h"
 
 #define GRL_LOG_DOMAIN_DEFAULT wc_log_domain
 GRL_LOG_DOMAIN(wc_log_domain);
@@ -191,6 +192,7 @@ grl_net_wc_init (GrlNetWc *wc)
 
   set_thread_context (wc);
   init_requester (wc);
+  init_mock_requester (wc);
 }
 
 static void
@@ -203,6 +205,7 @@ grl_net_wc_finalize (GObject *object)
 
   cache_down (wc);
   finalize_requester (wc);
+  finalize_mock_requester (wc);
 
   g_queue_free (wc->priv->pending);
   g_object_unref (wc->priv->session);
@@ -295,7 +298,10 @@ get_url_delayed (gpointer user_data)
     g_assert (c == d);
   }
 
-  get_url_now (c->self, c->url, c->headers, c->result, c->cancellable);
+  if (GRL_NET_IS_MOCKED)
+    get_url_mocked (c->self, c->url, c->headers, c->result, c->cancellable);
+  else
+    get_url_now (c->self, c->url, c->headers, c->result, c->cancellable);
 
   g_free (c->url);
   if (c->headers) {
@@ -321,7 +327,10 @@ get_url (GrlNetWc *self,
   g_get_current_time (&now);
 
   if ((now.tv_sec - priv->last_request.tv_sec) > priv->throttling) {
-    get_url_now (self, url, headers, result, cancellable);
+    if (GRL_NET_IS_MOCKED)
+      get_url_mocked (self, url, headers, result, cancellable);
+    else
+      get_url_now (self, url, headers, result, cancellable);
     g_get_current_time (&priv->last_request);
 
     return;
@@ -477,6 +486,7 @@ grl_net_wc_request_with_headers_hash_async (GrlNetWc *self,
   get_url (self, uri, headers, G_ASYNC_RESULT (result), cancellable);
 }
 
+
 /**
  * grl_net_wc_request_finish:
  * @self: a #GrlNetWc instance
@@ -515,10 +525,16 @@ grl_net_wc_request_finish (GrlNetWc *self,
     goto end_func;
   }
 
-  get_content(self, op, content, length);
+  if (GRL_NET_IS_MOCKED)
+    get_content_mocked (self, op, content, length);
+  else
+    get_content(self, op, content, length);
 
 end_func:
-  free_op_res (op);
+  if (GRL_NET_IS_MOCKED)
+    free_mock_op_res (op);
+  else
+    free_op_res (op);
 
   return ret;
 }



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