Preliminary version of the C unit tests run-time environment.
Added make rules for building and running C unit tests.

diff --git a/Make.rules.in b/Make.rules.in
index 91b061b..0eb61c7 100644
--- a/Make.rules.in
+++ b/Make.rules.in
@@ -59,10 +59,6 @@
 LINTFLAGS = @LINTFLAGS@
 ALLLINTFLAGS = $(LINTFLAGS) $(DEFS) $(OPTIONS) $(DIVINCL)
 WINAPI_CHECK = $(TOPSRCDIR)/tools/winapi_check/winapi_check
-WINETEST     = $(TOPOBJDIR)/programs/winetest/winetest
-RUNTEST      = $(TOPSRCDIR)/programs/winetest/runtest
-RUNTESTFLAGS = -q -P wine -M $(MODULE) -T $(TOPOBJDIR)
-TESTRESULTS  = $(PLTESTS:.pl=.ok) $(CTESTS:.c=.ok)
 WINEBUILD = $(TOPOBJDIR)/tools/winebuild/winebuild
 MAKEDEP   = $(TOPOBJDIR)/tools/makedep
 WRC       = $(TOPOBJDIR)/tools/wrc/wrc
@@ -74,6 +70,15 @@
 LIBUNICODE= -L$(TOPOBJDIR)/unicode -lwine_unicode
 LIBUUID   = -L$(TOPOBJDIR)/ole -lwine_uuid
 
+WINETEST     = $(TOPOBJDIR)/programs/winetest/winetest
+RUNTEST      = $(TOPSRCDIR)/programs/winetest/runtest
+RUNTESTFLAGS = -q -P wine -M $(MODULE) -T $(TOPOBJDIR)
+TESTRESULTS  = $(PLTESTS:.pl=.ok) $(CTESTS:.c=.ok)
+TESTPROGRAM  = tests/$(MODULE)_test
+TESTLIST     = tests/testlist.c
+TESTOBJS     = $(TESTMAIN) $(TESTLIST:.c=.o) $(CTESTS:.c=.o)
+TESTMAIN     = $(TOPOBJDIR)/programs/winetest/wtmain.o
+
 @SET_MAKE@
 
 # Installation infos
@@ -130,6 +135,9 @@
 .c.ln:
 	$(LINT) -c $(ALLLINTFLAGS) $< || ( $(RM) $@ && exit 1 )
 
+.c.ok:
+	$(RUNTEST) $(RUNTESTFLAGS) -p $(TESTPROGRAM) $< && touch $@
+
 .pl.ok:
 	$(RUNTEST) $(RUNTESTFLAGS) $< && touch $@
 
@@ -217,7 +225,7 @@
 	cd `dirname $@` && $(MAKE) depend
 
 depend: $(MAKEDEP) $(GEN_C_SRCS) $(SUBDIRS:%=%/__depend__)
-	$(MAKEDEP) $(DIVINCL) -C$(SRCDIR) $(C_SRCS) $(RC_SRCS) $(RC_SRCS16) $(MC_SRCS) $(EXTRA_SRCS) -C. $(GEN_C_SRCS)
+	$(MAKEDEP) $(DIVINCL) -C$(SRCDIR) $(C_SRCS) $(RC_SRCS) $(RC_SRCS16) $(MC_SRCS) $(EXTRA_SRCS) $(CTESTS) -C. $(GEN_C_SRCS)
 
 # Rules for cleaning
 
@@ -248,11 +256,28 @@
 
 test:: $(TESTRESULTS)
 
-$(TESTRESULTS): $(WINETEST)
+$(PLTESTS:.c=.ok): $(WINETEST)
+$(CTESTS:.c=.ok): $(TESTPROGRAM).so
 
 $(WINETEST):
 	cd $(TOPOBJDIR)/programs/winetest && $(MAKE) winetest
 
+$(TESTMAIN):
+	cd $(TOPOBJDIR)/programs/winetest && $(MAKE) wtmain.o
+
+$(TESTLIST): Makefile.in
+	$(TOPSRCDIR)/programs/winetest/make_ctests $(CTESTS) >$(TESTLIST) || $(RM) $(TESTLIST)
+
+$(TESTPROGRAM).so: $(TESTPROGRAM).spec.o $(TESTOBJS)
+	$(LDSHARED) $(LDDLLFLAGS) $(TESTPROGRAM).spec.o $(TESTOBJS) -o $@ $(LIBWINE) $(LIBS)
+
+$(TESTPROGRAM).tmp.o: $(TESTOBJS)
+	$(LDCOMBINE) $(TESTOBJS) -o $@
+	-strip --strip-unneeded $@
+
+$(TESTPROGRAM).spec.c: $(TESTPROGRAM).spec $(TESTPROGRAM).tmp.o $(WINEBUILD)
+	$(LDPATH) $(WINEBUILD) @DLLFLAGS@ -L$(DLLDIR) -sym $(TESTPROGRAM).tmp.o -o $@ -spec $(SRCDIR)/$(TESTPROGRAM).spec
+
 # Misc. rules
 
 $(SPEC_SRCS:.spec=.spec.c): $(WINEBUILD)
diff --git a/dlls/Makedll.rules.in b/dlls/Makedll.rules.in
index 1f95477..beba975 100644
--- a/dlls/Makedll.rules.in
+++ b/dlls/Makedll.rules.in
@@ -11,7 +11,6 @@
 
 DEFS       = @DLLFLAGS@ -D__WINE__ $(EXTRADEFS)
 LIBEXT     = @LIBEXT@
-SONAME     = lib$(MODULE).so
 IMPORTLIBS = $(IMPORTS:%=$(DLLDIR)/lib%.$(LIBEXT))
 SPEC_SRCS  = $(ALTNAMES:%=%.spec)
 ALL_OBJS   = $(MODULE).spec.o $(OBJS)
diff --git a/include/wine/test.h b/include/wine/test.h
new file mode 100644
index 0000000..7ce6543
--- /dev/null
+++ b/include/wine/test.h
@@ -0,0 +1,22 @@
+/*
+ * Definitions for Wine C unit tests.
+ *
+ * Copyright (C) 2002 Alexandre Julliard
+ */
+
+#ifndef __WINE_TEST_H
+#define __WINE_TEST_H
+
+/* debug level */
+extern int winetest_debug;
+
+/* current platform */
+extern const char *winetest_platform;
+
+extern void winetest_ok( int condition, const char *msg, const char *file, int line );
+
+#define START_TEST(name) void func_##name(void)
+
+#define ok(test,msg) winetest_ok( (test), (msg), __FILE__, __LINE__ )
+
+#endif  /* __WINE_TEST_H */
diff --git a/programs/winetest/make_ctests b/programs/winetest/make_ctests
new file mode 100755
index 0000000..ddda067
--- /dev/null
+++ b/programs/winetest/make_ctests
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+
+use strict;
+
+print "/* Automatically generated file; DO NOT EDIT!! */\n\n";
+
+my @testlist = @ARGV;
+foreach (@testlist)
+{
+    s!.*/([^/]+)\.c$!$1!;
+    printf "extern void func_%s(void);\n", $_;
+}
+
+print <<EOF;
+
+const struct
+{
+    const char *name;
+    void (*func)(void);
+} winetest_testlist[] =
+{
+EOF
+
+foreach (@testlist)
+{
+    printf "    { \"%s\", func_%s },\n", $_, $_;
+}
+print "    { 0, 0 }\n};\n";
diff --git a/programs/winetest/runtest b/programs/winetest/runtest
index dddcd5d..fefdd71 100755
--- a/programs/winetest/runtest
+++ b/programs/winetest/runtest
@@ -16,6 +16,7 @@
 Options:
     -q       quiet mode
     -v       verbose mode (can be specified multiple times)
+    -p prog  name of the program to run for C tests
     -I dir   prepend dir to Perl include path
     -P name  set the current platform name
     -M names set the module names to be tested
@@ -31,6 +32,7 @@
 
 my $topobjdir;
 my $infile;
+my $program;
 my @include_dirs;
 my @modules;
 
@@ -39,6 +41,7 @@
 {
     my $arg = shift @ARGV;
     if ($arg eq "-h") { usage; }
+    if ($arg eq "-p") { $program = shift @ARGV; next; }
     if ($arg eq "-q") { $ENV{WINETEST_DEBUG} = 0; next; }
     if ($arg eq "-v") { $ENV{WINETEST_DEBUG}++; next; }
     if ($arg eq "-P") { $platform = shift @ARGV; next; }
@@ -57,6 +60,12 @@
 # we must have found an input file
 usage unless defined($infile);
 
+if ($infile =~ /\.c$/ && !defined($program))
+{
+    # set program to the .c file base name if not specified otherwise
+    ($program = $infile) =~ s/\.c$//;
+}
+
 # check/detect topobjdir
 if (defined($topobjdir))
 {
@@ -74,20 +83,6 @@
     elsif (-f "../../../server/wineserver") { $topobjdir = "../../.."; }
 }
 
-# set environment variables needed for Wine
-if (defined($topobjdir))
-{
-    chop($topobjdir = `cd $topobjdir && pwd`);
-    $ENV{LD_LIBRARY_PATH} = $topobjdir . "/dlls:" . $topobjdir . ":" . $ENV{LD_LIBRARY_PATH};
-    $ENV{WINESERVER} ||= $topobjdir . "/server/wineserver";
-    $ENV{WINELOADER} ||= $topobjdir . "/wine";
-    $ENV{WINETEST_PLATFORM} = $platform || "wine";
-}
-else
-{
-    $ENV{WINETEST_PLATFORM} = $platform || "windows";
-}
-
 # check for include/ dir in script source directory and append it to search path
 my $basedir = $0;
 if ($basedir =~ /\//) { $basedir =~ s!/[^/]+$!!; }
@@ -101,10 +96,25 @@
     $ENV{WINEOPTIONS} .= "--dll " . join(',',@modules) . "=b";
 }
 
-# and now exec winetest
+# set environment variables needed for Wine
 if (defined($topobjdir))
 {
-    exec $topobjdir . "/programs/winetest/winetest", $infile, @ARGV;
+    chop($topobjdir = `cd $topobjdir && pwd`);
+    $ENV{LD_LIBRARY_PATH} = $topobjdir . "/dlls:" . $topobjdir . ":" . $ENV{LD_LIBRARY_PATH};
+    $ENV{WINESERVER} ||= $topobjdir . "/server/wineserver";
+    $ENV{WINELOADER} ||= $topobjdir . "/wine";
+    $ENV{WINETEST_PLATFORM} = $platform || "wine";
+    $ENV{WINEPRELOAD}=($program || ($topobjdir . "/programs/winetest/winetest")) . ".so";
+    # try to exec the wine loader directly; if it fails continue on to normal exec
+    exec $ENV{WINELOADER}, $infile, @ARGV;
 }
-exec "winetest", $infile, @ARGV;
-print STDERR "Could not exec winetest\n";
+else
+{
+    $ENV{WINETEST_PLATFORM} = $platform || "windows";
+}
+
+# and now exec the program
+$program ||= "winetest";
+exec $program, $infile, @ARGV;
+print STDERR "Could not exec $program\n";
+exit 1;
diff --git a/programs/winetest/wtmain.c b/programs/winetest/wtmain.c
new file mode 100644
index 0000000..52898e8
--- /dev/null
+++ b/programs/winetest/wtmain.c
@@ -0,0 +1,129 @@
+/*
+ * Main routine for Wine C unit tests.
+ *
+ * Copyright 2002 Alexandre Julliard
+ * Copyright 2002 Andriy Palamarchuk
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* debug level */
+int winetest_debug = 1;
+
+/* current platform */
+const char *winetest_platform = "windows";
+
+struct test
+{
+    const char  *name;
+    void       (*func)(void);
+};
+
+extern const struct test winetest_testlist[];
+static const struct test *current_test; /* test currently being run */
+
+static int successes;         /* number of successful tests */
+static int failures;          /* number of failures */
+static int todo_successes;    /* number of successful tests inside todo block */
+static int todo_failures;     /* number of failures inside todo block */
+static int todo_level;        /* current todo nesting level */
+
+/*
+ * Checks condition.
+ * Parameters:
+ *   - condition - condition to check;
+ *   - msg test description;
+ *   - file - test application source code file name of the check
+ *   - line - test application source code file line number of the check
+ */
+void winetest_ok( int condition, const char *msg, const char *file, int line )
+{
+    if (todo_level)
+    {
+        if (condition)
+        {
+            fprintf( stderr, "%s:%d: Test succeeded inside todo block", file, line );
+            if (msg && msg[0]) fprintf( stderr, ": %s", msg );
+            fputc( '\n', stderr );
+            todo_failures++;
+        }
+        else todo_successes++;
+    }
+    else
+    {
+        if (!condition)
+        {
+            fprintf( stderr, "%s:%d: Test failed", file, line );
+            if (msg && msg[0]) fprintf( stderr, ": %s", msg );
+            fputc( '\n', stderr );
+            failures++;
+        }
+        else successes++;
+    }
+}
+
+
+/* Find a test by name */
+static const struct test *find_test( const char *name )
+{
+    const struct test *test;
+    const char *p;
+    int len;
+
+    if ((p = strrchr( name, '/' ))) name = p + 1;
+    if ((p = strrchr( name, '\\' ))) name = p + 1;
+    len = strlen(name);
+    if (len > 2 && !strcmp( name + len - 2, ".c" )) len -= 2;
+
+    for (test = winetest_testlist; test->name; test++)
+    {
+        if (!strncmp( test->name, name, len ) && !test->name[len]) break;
+    }
+    return test->name ? test : NULL;
+}
+
+
+/* Run a named test, and return exit status */
+static int run_test( const char *name )
+{
+    const struct test *test;
+    int status;
+
+    if (!(test = find_test( name )))
+    {
+        fprintf( stderr, "Fatal: test '%s' does not exist.\n", name );
+        exit(1);
+    }
+    successes = failures = todo_successes = todo_failures = 0;
+    todo_level = 0;
+    current_test = test;
+    test->func();
+
+    if (winetest_debug)
+    {
+        fprintf( stderr, "%s: %d tests executed, %d marked as todo, %d %s.\n",
+                 name, successes + failures + todo_successes + todo_failures,
+                 todo_successes, failures + todo_failures,
+                 (failures + todo_failures != 1) ? "failures" : "failure" );
+    }
+    status = (failures + todo_failures < 255) ? failures + todo_failures : 255;
+    return status;
+}
+
+
+/* main function */
+int main( int argc, char **argv )
+{
+    char *p;
+
+    if ((p = getenv( "WINETEST_PLATFORM" ))) winetest_platform = p;
+    if ((p = getenv( "WINETEST_DEBUG" ))) winetest_debug = atoi(p);
+    if (!argv[1])
+    {
+        fprintf( stderr, "Usage: %s test_name\n", argv[0] );
+        exit(1);
+    }
+    exit( run_test(argv[1]) );
+}