# Signed-off-by: Thomas Kho <tkho@ucla.edu>
#
diff -Naur wine_cvs/configure.ac wine_local/configure.ac
--- wine_cvs/configure.ac	2005-02-24 15:46:24.000000000 -0800
+++ wine_local/configure.ac	2005-02-24 23:35:55.000000000 -0800
@@ -1706,6 +1706,7 @@
 dlls/wintrust/Makefile
 dlls/wow32/Makefile
 dlls/wsock32/Makefile
+dlls/wsock32/tests/Makefile
 dlls/wtsapi32/Makefile
 dlls/x11drv/Makefile
 documentation/Makefile
diff -Naur wine_cvs/dlls/wsock32/Makefile.in wine_local/dlls/wsock32/Makefile.in
--- wine_cvs/dlls/wsock32/Makefile.in	2003-10-10 18:09:15.000000000 -0700
+++ wine_local/dlls/wsock32/Makefile.in	2005-02-24 23:36:11.000000000 -0800
@@ -12,6 +12,8 @@
 
 RC_SRCS = version.rc
 
+SUBDIRS = tests
+
 @MAKE_DLL_RULES@
 
 ### Dependencies:
diff -Naur wine_cvs/dlls/wsock32/tests/Makefile.in wine_local/dlls/wsock32/tests/Makefile.in
--- wine_cvs/dlls/wsock32/tests/Makefile.in	1969-12-31 16:00:00.000000000 -0800
+++ wine_local/dlls/wsock32/tests/Makefile.in	2005-02-24 23:36:31.000000000 -0800
@@ -0,0 +1,13 @@
+TOPSRCDIR = @top_srcdir@
+TOPOBJDIR = ../../..
+SRCDIR    = @srcdir@
+VPATH     = @srcdir@
+TESTDLL   = wsock32.dll
+IMPORTS   = wsock32
+
+CTESTS = \
+	wsock32_main.c
+
+@MAKE_TEST_RULES@
+
+### Dependencies:
diff -Naur wine_cvs/dlls/wsock32/tests/wsock32_main.c wine_local/dlls/wsock32/tests/wsock32_main.c
--- wine_cvs/dlls/wsock32/tests/wsock32_main.c	1969-12-31 16:00:00.000000000 -0800
+++ wine_local/dlls/wsock32/tests/wsock32_main.c	2005-02-24 23:36:31.000000000 -0800
@@ -0,0 +1,344 @@
+/*
+ * Unit tests for 32-bit WinSock 1.1 functions in Wine
+ *
+ * Copyright (c) 2005 Thomas Kho, Fredy Garcia, Douglas Rosenberg
+ * standalone boilerplate copyright (c) 2004,2005 Dan Kegel
+ *
+ * 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; either
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+
+#include <windows.h>
+#include <winsock.h>
+#include <wtypes.h>
+#include <winerror.h>
+
+/* To build outside Wine tree, compile with cl -DSTANDALONE -D_X86_ wsock32_main.c wsock32.lib */
+#ifndef STANDALONE
+#include "wine/test.h"
+#else
+#include <stdarg.h>
+#include <stdio.h>
+#define START_TEST(name) main(int argc, char **argv)
+#define ok(condition, msg) \
+	do { \
+		if(!(condition)) \
+		{ \
+			fprintf(stderr,"failed at %d\n",__LINE__); \
+			exit(1); \
+		} \
+	} while(0)
+
+#define todo_wine
+static void trace(const char *s, ...)
+{
+	va_list elipsis;
+	va_start (elipsis, s);
+	vprintf(s, elipsis);
+	va_end(elipsis);
+}
+#endif
+
+/* clients threads to create */
+#define NUM_CLIENTS 64
+
+/* amount of data to transfer from each client to server */
+#define TEST_DATA_SIZE 145244
+
+/* max time (seconds) to run test.  
+   On a 650 Mhz Linux system with tcpdump running, it takes 8 seconds. */
+#define TEST_TIMEOUT 20
+
+/* we often pass this size by reference */
+int sizeofSOCKADDR_IN = sizeof(SOCKADDR_IN);
+
+/* global test data; server sends it to client, then client verifies it */
+char *gTestData;
+
+struct ThreadInfo {
+	HANDLE Handle;
+	DWORD ID;
+};
+
+struct BlockingServerConnection {
+	struct ThreadInfo serverThread;
+	SOCKET connectedSocket; /* socket to communicate with client */
+	SOCKADDR_IN clientAddr; /* client info */
+};
+
+static void test_Startup(void);
+static void test_ClientServerBlocking_1(void);
+static void test_Cleanup(void);
+
+static void BlockingClient(int *serverPort);
+static int BlockingServer_Init(int type, SOCKET *sock, SOCKADDR_IN *addr);
+static void BlockingServer(SOCKET *sock);
+static struct BlockingServerConnection * BlockingServerConnection_New(SOCKET sock, SOCKADDR_IN clientAddr);
+static void BlockingServerConnection_Run(struct BlockingServerConnection *t);
+static void BlockingServerConnection_Delete(struct BlockingServerConnection *c);
+
+static void BlockingClient(int *serverPort)
+{
+	SOCKET sock;
+	SOCKADDR_IN server;
+	HOSTENT *hp;
+	int connectError;
+	int totCharsReceived = 0;
+	int numCharsReceived;
+	int memSame;
+	char buf[1001];
+
+	/* create socket */
+	sock = socket(AF_INET, SOCK_STREAM, 0);
+	ok( sock != INVALID_SOCKET , "Error in socket()\n");
+	if (sock == INVALID_SOCKET) {
+		WSACleanup();
+		exit(0);
+	}
+
+	hp = gethostbyname("localhost");
+
+	server.sin_family = AF_INET;
+	server.sin_addr = *(struct in_addr *) hp->h_addr;
+	server.sin_port = *serverPort;
+
+	/* connect to server */
+	connectError = connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr));
+	ok( !connectError , "client cannot connect to host\n");
+	if(connectError) {
+		WSACleanup();
+		exit(0);
+	}
+
+	/* start receiving data from server */
+	while( totCharsReceived < TEST_DATA_SIZE ) {
+		numCharsReceived = recv(sock, buf, 1000, 0);
+		ok( numCharsReceived > 0, "socket was closed unexpectedly\n" );
+		
+		/* check received data againt global test data */
+		memSame = ! memcmp(buf,gTestData+totCharsReceived,numCharsReceived);
+		ok( memSame, "data integrity lost during transfer\n" );
+		totCharsReceived += numCharsReceived;
+	}
+}
+
+static int BlockingServer_Init(int type, SOCKET *sock, SOCKADDR_IN *addr)
+{
+	/* BlockingServer_Init creates socket sock of type type and returns assigned port number in addr.
+	   returns server port number */
+
+	SOCKADDR_IN tmpAddr;
+	int bindOK;
+	int listenReturn;
+
+	/* create socket */
+	*sock = socket(AF_INET, type, 0);
+	ok( *sock != INVALID_SOCKET , "Error in socket()\n");
+	if (*sock == INVALID_SOCKET) {
+		WSACleanup();
+		exit(0);
+	}
+
+	addr->sin_family = AF_INET;
+	addr->sin_addr.s_addr = INADDR_ANY;
+	addr->sin_port = htons(0);
+
+	/* bind socket to port */
+	bindOK = !bind(*sock, (const SOCKADDR *) addr, sizeofSOCKADDR_IN);
+	ok( bindOK , "Error binding client to socket\n");
+	if( !bindOK ) {
+		WSACleanup();
+		exit(0);
+	}
+
+	/* get port number */
+	getsockname(*sock, (SOCKADDR *) &tmpAddr, &sizeofSOCKADDR_IN);
+	addr->sin_port = tmpAddr.sin_port;
+
+	/* listen on port */
+	listenReturn = listen(*sock, NUM_CLIENTS);
+	ok(listenReturn != SOCKET_ERROR, "error listening on socket\n");
+
+	return addr->sin_port;
+}
+
+static void BlockingServer(SOCKET *sock)
+{
+	/* listens for incoming connections and accepts up to NUM_CLIENTS connections at once */
+	struct BlockingServerConnection *connections[NUM_CLIENTS];
+	int connIndex = 0;
+	SOCKET tmpSock;
+	SOCKADDR_IN tmpSockAddr;
+
+	/* we require one connection from each client thread */
+	for (connIndex = 0; connIndex < NUM_CLIENTS; connIndex++) {
+			/* accept connection */
+			tmpSock = accept(*sock, (SOCKADDR *) &tmpSockAddr, &sizeofSOCKADDR_IN);
+			ok(tmpSock != INVALID_SOCKET, "error accepting socket\n");
+
+			/* handle new connection */
+			connections[connIndex] = BlockingServerConnection_New(tmpSock, tmpSockAddr);
+	}
+
+	/* clean up connections */
+	for(connIndex = 0; connIndex < NUM_CLIENTS; connIndex++) {
+		BlockingServerConnection_Delete(connections[connIndex]);
+	}
+}
+
+static struct BlockingServerConnection * BlockingServerConnection_New(SOCKET sock, SOCKADDR_IN clientAddr)
+{
+	struct BlockingServerConnection *connection;
+	connection = malloc(sizeof(struct BlockingServerConnection));
+	memset(connection, 0, sizeof(struct BlockingServerConnection));
+
+	connection->connectedSocket = sock;
+	connection->clientAddr = clientAddr;
+
+	/* spawn thread to handle sending data */
+	connection->serverThread.Handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) &BlockingServerConnection_Run, connection, 0, &connection->serverThread.ID);
+
+	return connection;
+}
+
+static void BlockingServerConnection_Run(struct BlockingServerConnection *connection)
+{
+	/* BlockingServerConnection_Run handles data transfer */
+	int bClosed;
+	int totCharsSent = 0;
+	int numCharsSent;
+	const int charsPerSend = 2000;
+
+	/* loop and send data */
+	while( totCharsSent < TEST_DATA_SIZE ) {
+		numCharsSent = send(connection->connectedSocket, gTestData+totCharsSent, (totCharsSent + charsPerSend <= TEST_DATA_SIZE) ? charsPerSend : TEST_DATA_SIZE - totCharsSent, 0);
+		ok( numCharsSent != SOCKET_ERROR, "socket error\n" );
+
+		/* pass if send buffer is full */
+		if(numCharsSent == 0) {
+			Sleep(100);
+		}
+
+		totCharsSent += numCharsSent;
+	}
+
+	bClosed = !closesocket(connection->connectedSocket);
+	ok(bClosed,"Error closing socket\n");
+}
+
+static void BlockingServerConnection_Delete(struct BlockingServerConnection *c)
+{
+	/* wait for client to receive data before cleaning up */
+	WaitForSingleObject(c->serverThread.Handle, INFINITE);
+
+	free(c);
+}
+
+static void test_ClientServerBlocking_1(void)
+{
+	struct ThreadInfo serverThread;
+	struct ThreadInfo *clientThreads;
+	DWORD waitStatus;
+	SOCKET sock;
+	SOCKADDR_IN server;
+	int serverPort;
+	int threadIndex = 0;
+
+	/* create socket, bind server and start listening */
+	serverPort = BlockingServer_Init(SOCK_STREAM, &sock, &server);
+
+	/* start server thread */
+	trace("starting server thread\n");
+	serverThread.Handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) &BlockingServer, &sock, 0, &serverThread.ID);
+
+	/* start client threads */
+	clientThreads = malloc(sizeof(struct ThreadInfo) * NUM_CLIENTS);
+	memset(clientThreads, 0, sizeof(struct ThreadInfo) * NUM_CLIENTS);
+
+	for(threadIndex = 0; threadIndex < NUM_CLIENTS; threadIndex++) {
+		clientThreads[threadIndex].Handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) &BlockingClient, (void *) &serverPort, 0, &clientThreads[threadIndex].ID);
+	}
+	trace("%d clients started\n", NUM_CLIENTS);
+
+	/* server thread needs to end before cleaning up */
+	waitStatus = WaitForSingleObject(serverThread.Handle, TEST_TIMEOUT * 1000);
+	ok( waitStatus != WAIT_TIMEOUT, "test did not complete in time\n" );
+
+	/* wait for all clients to receive data before cleaning up */
+	for(threadIndex = 0; threadIndex < NUM_CLIENTS; threadIndex++) {
+		WaitForSingleObject(clientThreads[threadIndex].Handle, INFINITE);
+	}
+
+	free(clientThreads);
+}
+
+static void test_Startup(void)
+{
+	/* generate test data and initialize application */
+	WSADATA wsaData;
+	int wsastartup_result;
+	int versionOK;
+	int i;
+
+	srand(1);
+
+	/* fill out test data */
+	gTestData = malloc(TEST_DATA_SIZE);
+	for(i = 0; i < (TEST_DATA_SIZE/sizeof(int)); i++) {
+		*(((int *) gTestData) + i) = rand();
+	}
+
+	/* check for compatible winsock version */
+	wsastartup_result = WSAStartup(MAKEWORD(1,1), &wsaData);
+	ok((wsastartup_result == NO_ERROR), "Error in WSAStartup()\n");
+
+	versionOK = (LOBYTE(wsaData.wVersion) == 1) && (HIBYTE(wsaData.wVersion) == 1);
+	ok( versionOK , "WSAStartup returns an incompatible sockets version\n");
+	if ( !versionOK ) {
+		WSACleanup();
+		exit(0);
+	}
+
+	trace("startup ok\n");
+}
+
+static void test_Cleanup(void)
+{
+	int cleanupOK;
+
+	cleanupOK = ! WSACleanup();
+	ok( cleanupOK , "error in WSACleanup()\n");
+
+	free(gTestData);
+
+	trace("cleanup ok\n");
+}
+
+START_TEST(wsock32_main)
+{
+	const int numTests = 3;
+
+	trace("test 1 of %d:\n", numTests);
+	test_Startup();
+  
+	trace("test 2 of %d:\n", numTests);
+	test_ClientServerBlocking_1();
+	
+	trace("test 3 of %d:\n", numTests);
+	test_Cleanup();
+
+	trace("all " __FILE__ " tests done\n");
+}
