[libgda/LIBGDA_4.2] Added gda_rewrite_statement_for_null_parameters()



commit 3e369aaed9d383a2dc88efdf2c5e6517f70b319d
Author: Vivien Malerba <malerba gnome-db org>
Date:   Sun Aug 7 18:38:59 2011 +0200

    Added gda_rewrite_statement_for_null_parameters()

 doc/C/libgda-sections.txt             |    1 +
 libgda/gda-util.c                     |  199 +++++++++++++++++++++++++++++++++
 libgda/gda-util.h                     |    6 +-
 libgda/libgda.symbols                 |    1 +
 tests/parser/.gitignore               |    1 +
 tests/parser/Makefile.am              |    7 +-
 tests/parser/check_rewrite_for_null.c |  138 +++++++++++++++++++++++
 7 files changed, 350 insertions(+), 3 deletions(-)
---
diff --git a/doc/C/libgda-sections.txt b/doc/C/libgda-sections.txt
index 200f544..ee085b7 100644
--- a/doc/C/libgda-sections.txt
+++ b/doc/C/libgda-sections.txt
@@ -1597,6 +1597,7 @@ gda_meta_store_set_reserved_keywords_func
 <SUBSECTION>
 gda_compute_dml_statements
 gda_compute_select_statement_from_update
+gda_rewrite_statement_for_null_parameters
 gda_compute_unique_table_row_condition
 gda_compute_unique_table_row_condition_with_cnc
 <SUBSECTION>
diff --git a/libgda/gda-util.c b/libgda/gda-util.c
index af4fcd1..52a73a3 100644
--- a/libgda/gda-util.c
+++ b/libgda/gda-util.c
@@ -1363,6 +1363,205 @@ gda_compute_select_statement_from_update (GdaStatement *update_stmt, GError **er
 	return sel_stmt;
 }
 
+typedef struct  {
+	GdaSqlAnyPart *contents;
+	GdaSet *params;
+	GSList *expr_list; /* contains a list of #GdaSqlExpr after
+			    * gda_sql_any_part_foreach has been called */
+} NullData;
+
+static gboolean
+null_param_foreach_func (GdaSqlAnyPart *part, NullData *data , GError **error)
+{
+	if ((part->type != GDA_SQL_ANY_EXPR) || !((GdaSqlExpr*) part)->param_spec)
+		return TRUE;
+
+	if (!part->parent ||
+	    (part->parent->type != GDA_SQL_ANY_SQL_OPERATION) ||
+	    ((((GdaSqlOperation*) part->parent)->operator_type != GDA_SQL_OPERATOR_TYPE_EQ) &&
+	     (((GdaSqlOperation*) part->parent)->operator_type != GDA_SQL_OPERATOR_TYPE_DIFF)))
+		return TRUE;
+
+	GdaHolder *holder;
+	GdaSqlParamSpec *pspec = ((GdaSqlExpr*) part)->param_spec;
+	holder = gda_set_get_holder (data->params, pspec->name);
+	if (!holder)
+		return TRUE;
+	
+	const GValue *cvalue;
+	cvalue = gda_holder_get_value (holder);
+	if (!cvalue || (G_VALUE_TYPE (cvalue) != GDA_TYPE_NULL))
+		return TRUE;
+
+	GdaSqlOperation *op;
+	GdaSqlExpr *oexpr = NULL;
+	op = (GdaSqlOperation*) part->parent;
+	if (op->operands->data == part) {
+		if (op->operands->next)
+			oexpr = (GdaSqlExpr*) op->operands->next->data;
+	}
+	else
+		oexpr = (GdaSqlExpr*) op->operands->data;
+	if (oexpr && !g_slist_find (data->expr_list, oexpr)) /* handle situations where ##p1==##p2
+							      * and both p1 and p2 are set to NULL */
+		data->expr_list = g_slist_prepend (data->expr_list, part);
+	return TRUE;
+}
+
+static GdaSqlExpr *
+get_prev_expr (GSList *expr_list, GdaSqlAnyPart *expr)
+{
+	GSList *list;
+	for (list = expr_list; list && list->next; list = list->next) {
+		if ((GdaSqlAnyPart*) list->next->data == expr)
+			return (GdaSqlExpr*) list->data;
+	}
+	return NULL;
+}
+
+static gboolean
+null_param_unknown_foreach_func (GdaSqlAnyPart *part, NullData *data , GError **error)
+{
+	GdaSqlExpr *expr;
+	if ((part->type != GDA_SQL_ANY_EXPR) || !((GdaSqlExpr*) part)->param_spec)
+		return TRUE;
+
+	if (!part->parent || part->parent != data->contents)
+		return TRUE;
+
+	GdaHolder *holder;
+	GdaSqlParamSpec *pspec = ((GdaSqlExpr*) part)->param_spec;
+	holder = gda_set_get_holder (data->params, pspec->name);
+	if (!holder)
+		return TRUE;
+	
+	const GValue *cvalue;
+	cvalue = gda_holder_get_value (holder);
+	if (!cvalue || (G_VALUE_TYPE (cvalue) != GDA_TYPE_NULL))
+		return TRUE;
+
+	GSList *tmplist = NULL;
+	for (expr = get_prev_expr (((GdaSqlStatementUnknown*) data->contents)->expressions, part);
+	     expr;
+	     expr = get_prev_expr (((GdaSqlStatementUnknown*) data->contents)->expressions, (GdaSqlAnyPart*) expr)) {
+		gchar *str, *tmp;
+		if (!expr->value || (G_VALUE_TYPE (expr->value) != G_TYPE_STRING))
+			goto out;
+
+		str = (gchar*) g_value_get_string (expr->value);
+		if (!str || !*str) {
+			tmplist = g_slist_prepend (tmplist, expr);
+			continue;
+		}
+		for (tmp = str + strlen (str) - 1; tmp >= str; tmp --) {
+			if ((*tmp == ' ') || (*tmp == '\t') || (*tmp == '\n') || (*tmp == '\r'))
+				continue;
+			if (*tmp == '=') {
+				gchar *dup;
+				if ((tmp > str) && (*(tmp-1) == '!')) {
+					*(tmp-1) = 0;
+					dup = g_strdup_printf ("%s IS NOT NULL", str);
+				}
+				else {
+					*tmp = 0;
+					dup = g_strdup_printf ("%s IS NULL", str);
+				}
+				g_value_take_string (expr->value, dup);
+				if (tmplist) {
+					data->expr_list = g_slist_concat (tmplist, data->expr_list);
+					tmplist = NULL;
+				}
+				data->expr_list = g_slist_prepend (data->expr_list, part);
+				goto out;
+			}
+			else
+				goto out;
+		}
+		tmplist = g_slist_prepend (tmplist, expr);
+	}
+ out:
+	g_slist_free (tmplist);
+
+	return TRUE;
+}
+
+/**
+ * gda_rewrite_statement_for_null_parameters:
+ * @stmt: (transfer full): a #GdaSqlStatement
+ * @params: a #GdaSet to be used as parameters when executing @stmt
+ * @error: a place to store errors, or %NULL
+ *
+ * Modifies @stmt to take into account any parameter which might be %NULL: if @stmt contains the
+ * equivalent of "xxx = &lt;parameter definition&gt;" and if that parameter is in @params and
+ * its value is of type GDA_TYPE_NUL, then that part is replaced with "xxx IS NULL". It also
+ * handles the "xxx IS NOT NULL" transformation.
+ *
+ * This function is used by provider's implementations to make sure one can use parameters with
+ * NULL values in statements without having to rewrite statements, as database usually don't
+ * consider that "xxx = NULL" is the same as "xxx IS NULL" when using parameters.
+ *
+ * Returns: (transfer full): the modified @stmt statement, or %NULL if an error occurred
+ *
+ * Since: 4.2.9
+ */
+GdaSqlStatement *
+gda_rewrite_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *params, GError **error)
+{
+	g_return_val_if_fail (sqlst, sqlst);
+	if (!params)
+		return sqlst;
+	GSList *list;
+	for (list = params->holders; list; list = list->next) {
+		const GValue *cvalue;
+		cvalue = gda_holder_get_value ((GdaHolder*) list->data);
+		if (cvalue && (G_VALUE_TYPE (cvalue) == GDA_TYPE_NULL))
+			break;
+	}
+	if (!list || (sqlst->stmt_type == GDA_SQL_STATEMENT_NONE))
+		return sqlst; /* no modifications necessary */
+
+	NullData data;
+	data.contents = GDA_SQL_ANY_PART (sqlst->contents);
+	data.params = params;
+	data.expr_list = NULL;
+
+	if (sqlst->stmt_type == GDA_SQL_STATEMENT_UNKNOWN) {
+		if (! gda_sql_any_part_foreach (GDA_SQL_ANY_PART (sqlst->contents),
+						(GdaSqlForeachFunc) null_param_unknown_foreach_func,
+						&data, error)) {
+			gda_sql_statement_free (sqlst);
+			return NULL;
+		}
+		for (list = data.expr_list; list; list = list->next) {
+			((GdaSqlStatementUnknown*) data.contents)->expressions = 
+				g_slist_remove (((GdaSqlStatementUnknown*) data.contents)->expressions,
+						list->data);
+			gda_sql_expr_free ((GdaSqlExpr*) list->data);
+		}
+	}
+	else {
+		if (! gda_sql_any_part_foreach (GDA_SQL_ANY_PART (sqlst->contents),
+						(GdaSqlForeachFunc) null_param_foreach_func,
+						&data, error)) {
+			gda_sql_statement_free (sqlst);
+			return NULL;
+		}
+		for (list = data.expr_list; list; list = list->next) {
+			GdaSqlOperation *op;
+			op = (GdaSqlOperation*) (((GdaSqlAnyPart*) list->data)->parent);
+			op->operands = g_slist_remove (op->operands, list->data);
+			if (op->operator_type == GDA_SQL_OPERATOR_TYPE_EQ)
+				op->operator_type = GDA_SQL_OPERATOR_TYPE_ISNULL;
+			else
+				op->operator_type = GDA_SQL_OPERATOR_TYPE_ISNOTNULL;
+			gda_sql_expr_free ((GdaSqlExpr*) list->data);
+		}
+	}
+	g_slist_free (data.expr_list);
+	return sqlst;
+}
+
+
 static gboolean stmt_rewrite_insert_remove (GdaSqlStatementInsert *ins, GdaSet *params, GError **error);
 static gboolean stmt_rewrite_insert_default_keyword (GdaSqlStatementInsert *ins, GdaSet *params, GError **error);
 static gboolean stmt_rewrite_update_default_keyword (GdaSqlStatementUpdate *upd, GdaSet *params, GError **error);
diff --git a/libgda/gda-util.h b/libgda/gda-util.h
index 445c6a3..c905a26 100644
--- a/libgda/gda-util.h
+++ b/libgda/gda-util.h
@@ -75,11 +75,11 @@ gchar       *gda_text_to_alphanum (const gchar *text);
 gchar       *gda_alphanum_to_text (gchar *text);
 
 /*
- * Statement computation from meta store 
+ * Statement computation (using data from meta store) 
  */
 GdaSqlExpr      *gda_compute_unique_table_row_condition (GdaSqlStatementSelect *stsel, GdaMetaTable *mtable, 
 							 gboolean require_pk, GError **error);
-GdaSqlExpr       *gda_compute_unique_table_row_condition_with_cnc (GdaConnection *cnc,
+GdaSqlExpr      *gda_compute_unique_table_row_condition_with_cnc (GdaConnection *cnc,
 								   GdaSqlStatementSelect *stsel,
 								   GdaMetaTable *mtable, gboolean require_pk,
 								   GError **error);
@@ -88,6 +88,8 @@ gboolean         gda_compute_dml_statements (GdaConnection *cnc, GdaStatement *s
 					     GdaStatement **insert_stmt, GdaStatement **update_stmt, GdaStatement **delete_stmt, 
 					     GError **error);
 GdaSqlStatement *gda_compute_select_statement_from_update (GdaStatement *update_stmt, GError **error);
+GdaSqlStatement *gda_rewrite_statement_for_null_parameters (GdaSqlStatement *sqlst, GdaSet *params,
+							    GError **error);
 
 /*
  * DSN and connection string manipulations
diff --git a/libgda/libgda.symbols b/libgda/libgda.symbols
index f1a0801..9609890 100644
--- a/libgda/libgda.symbols
+++ b/libgda/libgda.symbols
@@ -526,6 +526,7 @@
 	gda_repetitive_statement_get_template_set
 	gda_repetitive_statement_get_type
 	gda_repetitive_statement_new
+	gda_rewrite_statement_for_null_parameters
 	gda_rfc1738_decode
 	gda_rfc1738_encode
 	gda_row_get_length
diff --git a/tests/parser/.gitignore b/tests/parser/.gitignore
index 2da0e39..792aa0e 100644
--- a/tests/parser/.gitignore
+++ b/tests/parser/.gitignore
@@ -4,3 +4,4 @@ check_normalization
 check_dml_comp
 check_script
 check_rewrite_for_default
+check_rewrite_for_null
diff --git a/tests/parser/Makefile.am b/tests/parser/Makefile.am
index 807530e..f8d71ea 100644
--- a/tests/parser/Makefile.am
+++ b/tests/parser/Makefile.am
@@ -8,7 +8,7 @@ AM_CPPFLAGS = \
 
 TESTS_ENVIRONMENT = GDA_TOP_SRC_DIR="$(abs_top_srcdir)" GDA_TOP_BUILD_DIR="$(abs_top_builddir)"
 TESTS = check_parser check_validation check_normalization check_dml_comp check_script check_rewrite_for_default
-check_PROGRAMS = check_parser check_validation check_normalization check_dml_comp check_script check_rewrite_for_default
+check_PROGRAMS = check_parser check_validation check_normalization check_dml_comp check_script check_rewrite_for_default check_rewrite_for_null
 
 check_parser_SOURCES = check_parser.c
 check_parser_LDADD = \
@@ -40,6 +40,11 @@ check_rewrite_for_default_LDADD = \
 	$(top_builddir)/libgda/libgda-4.0.la \
 	$(LIBGDA_LIBS)
 
+check_rewrite_for_null_SOURCES = check_rewrite_for_null.c
+check_rewrite_for_null_LDADD = \
+	$(top_builddir)/libgda/libgda-5.0.la \
+	$(COREDEPS_LIBS)
+
 
 EXTRA_DIST = testdata.xml testvalid.xml testscripts.xml \
 	scripts/mysql_employees.sql \
diff --git a/tests/parser/check_rewrite_for_null.c b/tests/parser/check_rewrite_for_null.c
new file mode 100644
index 0000000..bdc78f6
--- /dev/null
+++ b/tests/parser/check_rewrite_for_null.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include <libgda/libgda.h>
+#include <sql-parser/gda-sql-parser.h>
+#include <string.h>
+
+typedef struct {
+	gchar *id;
+        gchar *sql;
+	gchar *result;
+} ATest;
+
+ATest tests[] = {
+        {"T1",
+	 "select * from mytable where id = ##id::int::null AND name = ##name::string",
+	 "{\"sql\":\"select * from mytable where id = ##id::int::null AND name = ##name::string\",\"stmt_type\":\"SELECT\",\"contents\":{\"distinct\":\"false\",\"fields\":[{\"expr\":{\"value\":\"*\"}}],\"from\":{\"targets\":[{\"expr\":{\"value\":\"mytable\"},\"table_name\":\"mytable\"}]},\"where\":{\"operation\":{\"operator\":\"AND\",\"operand0\":{\"operation\":{\"operator\":\"IS NULL\",\"operand0\":{\"value\":\"id\"}}},\"operand1\":{\"operation\":{\"operator\":\"=\",\"operand0\":{\"value\":\"name\"},\"operand1\":{\"value\":null,\"param_spec\":{\"name\":\"name\",\"descr\":null,\"type\":\"string\",\"is_param\":true,\"nullok\":false}}}}}}}}"},
+        {"T2",
+	 "select * from mytable where ##id::int::null = ##id::int::null AND name = ##name::string",
+	 "{\"sql\":\"select * from mytable where ##id::int::null = ##id::int::null AND name = ##name::string\",\"stmt_type\":\"SELECT\",\"contents\":{\"distinct\":\"false\",\"fields\":[{\"expr\":{\"value\":\"*\"}}],\"from\":{\"targets\":[{\"expr\":{\"value\":\"mytable\"},\"table_name\":\"mytable\"}]},\"where\":{\"operation\":{\"operator\":\"AND\",\"operand0\":{\"operation\":{\"operator\":\"IS NULL\",\"operand0\":{\"value\":null,\"param_spec\":{\"name\":\"id\",\"descr\":null,\"type\":\"int\",\"is_param\":true,\"nullok\":true}}}},\"operand1\":{\"operation\":{\"operator\":\"=\",\"operand0\":{\"value\":\"name\"},\"operand1\":{\"value\":null,\"param_spec\":{\"name\":\"name\",\"descr\":null,\"type\":\"string\",\"is_param\":true,\"nullok\":false}}}}}}}}"},
+        {"T3",
+	 "select * from mytable where id!=##id::int::null AND name != ##name::string",
+	 "{\"sql\":\"select * from mytable where id!=##id::int::null AND name != ##name::string\",\"stmt_type\":\"SELECT\",\"contents\":{\"distinct\":\"false\",\"fields\":[{\"expr\":{\"value\":\"*\"}}],\"from\":{\"targets\":[{\"expr\":{\"value\":\"mytable\"},\"table_name\":\"mytable\"}]},\"where\":{\"operation\":{\"operator\":\"AND\",\"operand0\":{\"operation\":{\"operator\":\"IS NOT NULL\",\"operand0\":{\"value\":\"id\"}}},\"operand1\":{\"operation\":{\"operator\":\"!=\",\"operand0\":{\"value\":\"name\"},\"operand1\":{\"value\":null,\"param_spec\":{\"name\":\"name\",\"descr\":null,\"type\":\"string\",\"is_param\":true,\"nullok\":false}}}}}}}}"},
+        {"T4",
+	 "select * from mytable where ##id::int::null != ##id::int::null AND name = ##name::string",
+	 "{\"sql\":\"select * from mytable where ##id::int::null != ##id::int::null AND name = ##name::string\",\"stmt_type\":\"SELECT\",\"contents\":{\"distinct\":\"false\",\"fields\":[{\"expr\":{\"value\":\"*\"}}],\"from\":{\"targets\":[{\"expr\":{\"value\":\"mytable\"},\"table_name\":\"mytable\"}]},\"where\":{\"operation\":{\"operator\":\"AND\",\"operand0\":{\"operation\":{\"operator\":\"IS NOT NULL\",\"operand0\":{\"value\":null,\"param_spec\":{\"name\":\"id\",\"descr\":null,\"type\":\"int\",\"is_param\":true,\"nullok\":true}}}},\"operand1\":{\"operation\":{\"operator\":\"=\",\"operand0\":{\"value\":\"name\"},\"operand1\":{\"value\":null,\"param_spec\":{\"name\":\"name\",\"descr\":null,\"type\":\"string\",\"is_param\":true,\"nullok\":false}}}}}}}}"},
+        {"T5",
+	 "pragme mine = ##id::int::null or name = ##name::string",
+	 "{\"sql\":\"pragme mine = ##id::int::null or name = ##name::string\",\"stmt_type\":\"UNKNOWN\",\"contents\":[{\"value\":\"pragme\"},{\"value\":\" \"},{\"value\":\"mine\"},{\"value\":\" \"},{\"value\":\" IS NULL\"},{\"value\":\" \"},{\"value\":\"or\"},{\"value\":\" \"},{\"value\":\"name\"},{\"value\":\" \"},{\"value\":\"=\"},{\"value\":\" \"},{\"value\":null,\"param_spec\":{\"name\":\"name\",\"descr\":null,\"type\":\"string\",\"is_param\":true,\"nullok\":false}}]}"},
+        {"T6",
+	 "pragme mine != ##id::int::null or name =##id::string::null AND col=##name::string",
+	 "{\"sql\":\"pragme mine != ##id::int::null or name =##id::string::null AND col=##name::string\",\"stmt_type\":\"UNKNOWN\",\"contents\":[{\"value\":\"pragme\"},{\"value\":\" \"},{\"value\":\"mine\"},{\"value\":\" \"},{\"value\":\" IS NOT NULL\"},{\"value\":\" \"},{\"value\":\"or\"},{\"value\":\" \"},{\"value\":\"name\"},{\"value\":\" \"},{\"value\":\" IS NULL\"},{\"value\":\" \"},{\"value\":\"AND\"},{\"value\":\" \"},{\"value\":\"col=\"},{\"value\":null,\"param_spec\":{\"name\":\"name\",\"descr\":null,\"type\":\"string\",\"is_param\":true,\"nullok\":false}}]}"},
+	{"T7",
+	 "UPDATE mytable set id=##id::int::null, name=##name::string",
+	 "{\"sql\":\"UPDATE mytable set id=##id::int::null, name=##name::string\",\"stmt_type\":\"UPDATE\",\"contents\":{\"table\":\"mytable\",\"fields\":[\"id\",\"name\"],\"expressions\":[{\"value\":null,\"param_spec\":{\"name\":\"id\",\"descr\":null,\"type\":\"int\",\"is_param\":true,\"nullok\":true}},{\"value\":null,\"param_spec\":{\"name\":\"name\",\"descr\":null,\"type\":\"string\",\"is_param\":true,\"nullok\":false}}]}}"},
+};
+
+static gboolean
+do_test (ATest *test)
+{
+	GdaSqlParser *parser;
+	GdaStatement *stmt;
+	GError *error = NULL;
+	gchar *tmp;
+
+	g_print ("** test %s\n", test->id);
+	parser = gda_sql_parser_new ();
+
+	GdaSet *params;
+	GValue *nv;
+	GdaHolder *holder;
+	stmt = gda_sql_parser_parse_string (parser, test->sql, NULL, &error);
+	g_object_unref (parser);
+	if (!stmt) {
+		g_print ("Parsing error: %s\n", error && error->message ? error->message : "No detail");
+		return FALSE;
+	}
+
+	if (! gda_statement_get_parameters (stmt, &params, &error)) {
+		g_print ("Error: %s\n", error && error->message ? error->message : "No detail");
+		return FALSE;
+	}
+	g_assert (gda_set_set_holder_value (params, NULL, "name", "zzz"));
+	nv = gda_value_new_null ();
+	holder = gda_set_get_holder (params, "id");
+	g_assert (gda_holder_set_value (holder, nv, NULL));
+	gda_value_free (nv);
+
+	GdaSqlStatement *sqlst;
+	g_object_get (stmt, "structure", &sqlst, NULL);
+
+	sqlst = gda_rewrite_statement_for_null_parameters (sqlst, params, &error);
+	if (!sqlst) {
+		g_print ("Rewrite error: %s\n", error && error->message ? error->message : "No detail");
+		return FALSE;
+	}
+	g_object_set (stmt, "structure", sqlst, NULL);
+
+	/* SQL rendering */
+	tmp = gda_statement_to_sql_extended (stmt, NULL, NULL, GDA_STATEMENT_SQL_PARAMS_SHORT,
+					     NULL, &error);
+	if (!tmp) {
+		g_print ("Rendering error: %s\n", error && error->message ? error->message : "No detail");
+		return FALSE;
+	}
+	/*g_print ("SQL after mod: [%s]\n", tmp);*/
+	g_free (tmp);
+
+	tmp = gda_sql_statement_serialize (sqlst);
+	if (!tmp) {
+		g_print ("Error: gda_sql_statement_serialize() failed\n");
+		return FALSE;
+	}
+	else if (strcmp (test->result, tmp)) {
+		g_print ("Exp: [%s]\nGot: [%s]\n", test->result, tmp);
+		return FALSE;
+	}
+
+	g_free (tmp);
+	gda_sql_statement_free (sqlst);
+
+	g_object_unref (stmt);
+	return TRUE;
+}
+
+int main()
+{
+	gda_init();
+	guint i;
+	gint n_errors = 0;
+
+	for (i = 0; i < sizeof (tests) / sizeof (ATest); i++) {
+		ATest *test = &tests[i];
+
+		if (! do_test (test))
+			n_errors++;
+	}
+
+	if (n_errors == 0)
+		g_print ("Ok: %d tests passed\n", i);
+	else
+		g_print ("Failed: %d tests total, %d failed\n", i, n_errors);
+	return n_errors ? 1 : 0;
+}



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