glibrdf

GLib wrapper for the Redland RDF library
git clone https://code.djc.id.au/git/glibrdf/
commit 8510b2178d608dd9f3dbc84e8c1c340e189d9987
parent dda3cfd1ff2b230838f01e62b6ef468526ee9c3f
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sun, 22 Jul 2012 16:59:59 +1000

handle timezones for datetime; added tests

Diffstat:
M.gitignore | 1+
MMakefile | 15++++++++++++++-
Mglibrdf.c | 29+++++++++++++++++++++++------
Atest-data.xml | 10++++++++++
Atest_literal_gvalue.c | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests.c | 17+++++++++++++++++
6 files changed, 209 insertions(+), 7 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,3 @@
 /glibrdf.pc
 /libglibrdf.so*
+/tests
diff --git a/Makefile b/Makefile
@@ -19,6 +19,7 @@ LDFLAGS ?= -Wl,--as-needed -Wl,-O1
 LIBS = $(shell pkg-config --libs $(REQUIRES))
 
 OBJECTS = glibrdf.o
+TEST_OBJECTS = tests.o test_literal_gvalue.o
 
 .PHONY: all
 all: $(LIB) $(NAME).pc
@@ -35,9 +36,21 @@ $(LIB): $(OBJECTS)
 
 glibrdf.o: glibrdf.h
 
+.PHONY: check
+check: tests
+	env MALLOC_CHECK_=2 \
+	    G_DEBUG="fatal_warnings fatal_criticals" \
+	    G_SLICE="debug-blocks" \
+	    gtester ./tests --verbose
+
+tests: $(OBJECTS) $(TEST_OBJECTS)
+	$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+test_literal_gvalue.o: glibrdf.h
+
 .PHONY: clean
 clean:
-	rm -f $(LIB) $(OBJECTS) $(NAME).pc
+	rm -f $(LIB) $(OBJECTS) $(TEST_OBJECTS) $(NAME).pc
 
 .PHONY: install
 install:
diff --git a/glibrdf.c b/glibrdf.c
@@ -22,12 +22,30 @@ GType librdf_node_get_gtype(void) {
 
 // glib should do this for me >:(
 static GDate *parse_iso8601_date(const gchar *s) {
-    struct tm tm;
-    if (*strptime(s, "%Y-%m-%d", &tm) != '\0')
+    struct tm tm = {0};
+    if (*strptime(s, "%F", &tm) != '\0')
         return NULL;
     return g_date_new_dmy(tm.tm_mday, 1 + tm.tm_mon, 1900 + tm.tm_year);
 }
 
+// g_time_val_from_iso8601 doesn't preserve timezone >:(((
+static GDateTime *parse_iso8601_datetime(const gchar *s) {
+    struct tm tm = {0};
+    // parse the date and time, timezone will be left at the end
+    const gchar *rest = strptime(s, "%FT%T", &tm);
+    GTimeZone *tz = g_time_zone_new(rest);
+    g_return_val_if_fail(tz != NULL, NULL);
+    GDateTime *result = g_date_time_new(tz,
+            1900 + tm.tm_year,
+            1 + tm.tm_mon,
+            tm.tm_mday,
+            tm.tm_hour,
+            tm.tm_min,
+            (gdouble) tm.tm_sec);
+    g_time_zone_unref(tz);
+    return result;
+}
+
 // XXX handle parse failures more gracefully?
 // XXX should have some kind of registry so that callers can add new types
 void librdf_node_get_literal_gvalue(librdf_node *node, GValue *value_out) {
@@ -60,11 +78,10 @@ void librdf_node_get_literal_gvalue(librdf_node *node, GValue *value_out) {
     }
     if (g_strcmp0(datatype_uri_string,
             "http://www.w3.org/TR/xmlschema-2/#datetime") == 0) {
-        GTimeVal tv;
-        bool parsed = g_time_val_from_iso8601(lv, &tv);
-        g_return_if_fail(parsed);
+        GDateTime *datetime = parse_iso8601_datetime(lv);
+        g_return_if_fail(datetime != NULL);
         g_value_init(value_out, G_TYPE_DATE_TIME);
-        g_value_set_boxed(value_out, g_date_time_new_from_timeval_utc(&tv));
+        g_value_set_boxed(value_out, datetime);
         return;
     }
     g_warning("Unhandled RDF type %s", librdf_uri_as_string(datatype_uri));
diff --git a/test-data.xml b/test-data.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+         xmlns:ex="http://example.com/">
+    <rdf:Description rdf:about="http://example.com/Resource">
+        <ex:untyped>blah</ex:untyped>
+        <ex:integer rdf:datatype="http://www.w3.org/2001/XMLSchema#integer">123</ex:integer>
+        <ex:date rdf:datatype="http://www.w3.org/TR/xmlschema-2/#date">1986-08-16</ex:date>
+        <ex:datetime rdf:datatype="http://www.w3.org/TR/xmlschema-2/#datetime">2012-07-22T15:57:41+10:00</ex:datetime>
+    </rdf:Description>
+</rdf:RDF>
diff --git a/test_literal_gvalue.c b/test_literal_gvalue.c
@@ -0,0 +1,144 @@
+
+/*
+ * test_literal_gvalue.c, part of glibrdf
+ * Copyright 2012 Dan Callaghan <djc@djc.id.au>
+ * Licensed under GPLv3
+ */
+
+#include "glibrdf.h"
+
+#define FIXTURE_TYPE LiteralGValueFixture
+#define SETUP literal_gvalue_setup
+#define TEARDOWN literal_gvalue_teardown
+#define DATA const void *user_data G_GNUC_UNUSED
+#define PACKAGE "/literal_gvalue/"
+#define TEST_ADD(func) g_test_add(PACKAGE #func, FIXTURE_TYPE, NULL, SETUP, func, TEARDOWN)
+
+typedef struct {
+    librdf_world *world;
+    librdf_storage *storage;
+    librdf_model *model;
+} FIXTURE_TYPE;
+
+static void SETUP(FIXTURE_TYPE *fixture, DATA) {
+    fixture->world = librdf_new_world();
+    g_assert(fixture->world != NULL);
+    librdf_world_open(fixture->world);
+    fixture->storage = librdf_new_storage(fixture->world,
+            "hashes", NULL, "hash-type='memory'");
+    g_assert(fixture->storage != NULL);
+    fixture->model = librdf_new_model(fixture->world, fixture->storage, "");
+    g_assert(fixture->model != NULL);
+
+    librdf_parser *parser = librdf_new_parser(fixture->world,
+            NULL, "application/rdf+xml", NULL);
+    g_assert(parser != NULL);
+    librdf_uri *source_uri = librdf_new_uri_from_filename(
+            fixture->world, "test-data.xml");
+    g_assert(source_uri != NULL);
+    int parse_error = librdf_parser_parse_into_model(parser,
+            source_uri, NULL, fixture->model);
+    g_assert(!parse_error);
+    librdf_free_uri(source_uri);
+    librdf_free_parser(parser);
+}
+
+static void TEARDOWN(FIXTURE_TYPE *fixture, DATA) {
+    librdf_free_model(fixture->model);
+    librdf_free_storage(fixture->storage);
+    librdf_free_world(fixture->world);
+}
+
+static void test_untyped(FIXTURE_TYPE *fixture, DATA) {
+    librdf_node *subj = librdf_new_node_from_uri_string(fixture->world,
+            (const unsigned char *)"http://example.com/Resource");
+    g_assert(subj != NULL);
+    librdf_node *prop = librdf_new_node_from_uri_string(fixture->world,
+            (const unsigned char *)"http://example.com/untyped");
+    g_assert(prop != NULL);
+    librdf_node *obj = librdf_model_get_target(fixture->model, subj, prop);
+    g_assert(obj != NULL);
+    GValue value = {0};
+    librdf_node_get_literal_gvalue(obj, &value);
+    g_assert(G_VALUE_HOLDS(&value, G_TYPE_STRING));
+    g_assert_cmpstr(g_value_get_string(&value), ==, "blah");
+    g_value_unset(&value);
+    librdf_free_node(obj);
+    librdf_free_node(prop);
+    librdf_free_node(subj);
+}
+
+static void test_integer(FIXTURE_TYPE *fixture, DATA) {
+    librdf_node *subj = librdf_new_node_from_uri_string(fixture->world,
+            (const unsigned char *)"http://example.com/Resource");
+    g_assert(subj != NULL);
+    librdf_node *prop = librdf_new_node_from_uri_string(fixture->world,
+            (const unsigned char *)"http://example.com/integer");
+    g_assert(prop != NULL);
+    librdf_node *obj = librdf_model_get_target(fixture->model, subj, prop);
+    g_assert(obj != NULL);
+    GValue value = {0};
+    librdf_node_get_literal_gvalue(obj, &value);
+    g_assert(G_VALUE_HOLDS(&value, G_TYPE_INT64));
+    g_assert_cmpint(g_value_get_int64(&value), ==, 123);
+    g_value_unset(&value);
+    librdf_free_node(obj);
+    librdf_free_node(prop);
+    librdf_free_node(subj);
+}
+
+static void test_date(FIXTURE_TYPE *fixture, DATA) {
+    librdf_node *subj = librdf_new_node_from_uri_string(fixture->world,
+            (const unsigned char *)"http://example.com/Resource");
+    g_assert(subj != NULL);
+    librdf_node *prop = librdf_new_node_from_uri_string(fixture->world,
+            (const unsigned char *)"http://example.com/date");
+    g_assert(prop != NULL);
+    librdf_node *obj = librdf_model_get_target(fixture->model, subj, prop);
+    g_assert(obj != NULL);
+    GValue value = {0};
+    librdf_node_get_literal_gvalue(obj, &value);
+    g_assert(G_VALUE_HOLDS(&value, G_TYPE_DATE));
+    GDate *date = (GDate *)g_value_get_boxed(&value);
+    g_assert_cmpuint(g_date_get_year(date), ==, 1986);
+    g_assert_cmpuint(g_date_get_month(date), ==, 8);
+    g_assert_cmpuint(g_date_get_day(date), ==, 16);
+    g_value_unset(&value);
+    librdf_free_node(obj);
+    librdf_free_node(prop);
+    librdf_free_node(subj);
+}
+
+static void test_datetime(FIXTURE_TYPE *fixture, DATA) {
+    librdf_node *subj = librdf_new_node_from_uri_string(fixture->world,
+            (const unsigned char *)"http://example.com/Resource");
+    g_assert(subj != NULL);
+    librdf_node *prop = librdf_new_node_from_uri_string(fixture->world,
+            (const unsigned char *)"http://example.com/datetime");
+    g_assert(prop != NULL);
+    librdf_node *obj = librdf_model_get_target(fixture->model, subj, prop);
+    g_assert(obj != NULL);
+    GValue value = {0};
+    librdf_node_get_literal_gvalue(obj, &value);
+    g_assert(G_VALUE_HOLDS(&value, G_TYPE_DATE_TIME));
+    GDateTime *datetime = (GDateTime *)g_value_get_boxed(&value);
+    g_assert_cmpint(g_date_time_get_year(datetime),             ==, 2012);
+    g_assert_cmpint(g_date_time_get_month(datetime),            ==, 7);
+    g_assert_cmpint(g_date_time_get_day_of_month(datetime),     ==, 22);
+    g_assert_cmpint(g_date_time_get_hour(datetime),             ==, 15);
+    g_assert_cmpint(g_date_time_get_minute(datetime),           ==, 57);
+    g_assert_cmpfloat(g_date_time_get_seconds(datetime),        ==, 41.0);
+    g_assert_cmpint(g_date_time_get_utc_offset(datetime),       ==,
+            /* 10 hours in us */ 10 * 60 * 60 * 1000000LL);
+    g_value_unset(&value);
+    librdf_free_node(obj);
+    librdf_free_node(prop);
+    librdf_free_node(subj);
+}
+
+void add_literal_gvalue_tests(void) {
+    TEST_ADD(test_untyped);
+    TEST_ADD(test_integer);
+    TEST_ADD(test_date);
+    TEST_ADD(test_datetime);
+}
diff --git a/tests.c b/tests.c
@@ -0,0 +1,17 @@
+
+/*
+ * tests.c, part of glibrdf
+ * Copyright 2012 Dan Callaghan <djc@djc.id.au>
+ * Licensed under GPLv3
+ */
+
+#include <glib-object.h>
+
+void add_literal_gvalue_tests(void);
+
+int main(int argc, char *argv[]) {
+    g_type_init();
+    g_test_init(&argc, &argv, NULL);
+    add_literal_gvalue_tests();
+    return g_test_run();
+}