[libgda] Added gda_rewrite_statement_for_null_parameters()
- From: Vivien Malerba <vivien src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgda] Added gda_rewrite_statement_for_null_parameters()
- Date: Sun, 7 Aug 2011 16:41:51 +0000 (UTC)
commit 84c664cac35cbd5e119c29b07f20b2618778cbc4
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 95fe56c..b8fa1c9 100644
--- a/doc/C/libgda-sections.txt
+++ b/doc/C/libgda-sections.txt
@@ -1585,6 +1585,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 92dd59a..a3c04d1 100644
--- a/libgda/gda-util.c
+++ b/libgda/gda-util.c
@@ -1361,6 +1361,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 = <parameter definition>" 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 1d2e665..51ab011 100644
--- a/libgda/gda-util.h
+++ b/libgda/gda-util.h
@@ -85,11 +85,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);
@@ -98,6 +98,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 0fa9cb1..d13e5b0 100644
--- a/libgda/libgda.symbols
+++ b/libgda/libgda.symbols
@@ -507,6 +507,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 dc26c52..415b7e1 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-5.0.la \
$(COREDEPS_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, ¶ms, &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]