# HG changeset patch
# Parent f0bcb19edef6672cbaabe7210866acdaf226967f
Implement preliminary thread-safe I/O testsuite.

diff --git a/mingwrt/tests/Makefile.in b/mingwrt/tests/Makefile.in
--- a/mingwrt/tests/Makefile.in
+++ b/mingwrt/tests/Makefile.in
@@ -100,11 +100,11 @@ autotest_command = autom4te --language a
 autotest_missing = $(call missing,autom4te,Unable to compile the testsuite)
 
 testsuite: %: %.at
 	$(AUTOTEST_COMPILE) -o $* $<
 
-testsuite: headers.at ansiprintf.at logarithms.at powerfunc.at
+testsuite: $(filter-out testsuite.at,$(notdir $(wildcard ${srcdir}/*.at)))
 
 # Display a diagnostic message, explaining that any specified program
 # is required, but has not been installed.
 #
 define missing
diff --git a/mingwrt/tests/testsuite.at.in b/mingwrt/tests/testsuite.at.in
--- a/mingwrt/tests/testsuite.at.in
+++ b/mingwrt/tests/testsuite.at.in
@@ -86,10 +86,11 @@ MINGW_AT_LANG([C])
 # Individual test groups are specified separately, via the following
 # collection of autotest M4 include files.
 #
 m4_include([headers.at])
 m4_include([ansiprintf.at])
+m4_include([xfdhooks.at])
 m4_include([logarithms.at])
 m4_include([powerfunc.at])
 
 # vim: filetype=config formatoptions=croql
 # $RCSfile$: end of file
diff --git a/mingwrt/tests/xfdhooks.at b/mingwrt/tests/xfdhooks.at
new file mode 100644
--- /dev/null
+++ b/mingwrt/tests/xfdhooks.at
@@ -0,0 +1,212 @@
+# xfdhooks.at
+#
+# Autotest module to verify correct operation of the MinGW.org CRT
+# thread-safe I/O extensions.
+#
+# $Id$
+#
+# Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+# Copyright (C) 2017, MinGW.org Project
+#
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+#
+#
+# All tests specified herein are written in the C language.
+#
+MINGW_AT_LANG([C])
+
+# MINGW_AT_XFD_MULTITHREAD_IFELSE( DESC, CODE_IF_YES, CODE_IF_NO )
+# ----------------------------------------------------------------
+# If DESC begins with the initial sub-string "Multi", emit CODE_IF_YES;
+# otherwise, emit CODE_IF_NO.
+#
+m4_define([MINGW_AT_XFD_MULTITHREAD_IFELSE],dnl
+[m4_bmatch([$1],[^Multi],[$2],[$3])dnl
+])# MINGW_AT_XFD_MULTITHREAD_IFELSE
+
+# MINGW_AT_XFD_C_HEADER( DESC )
+# -----------------------------
+# Emit common header code for thread-safe I/O function tests; if DESC
+# begins with the initial sub-string "Multi", include additional code
+# which is specific to tests running in multiple threads.
+#
+m4_define([MINGW_AT_XFD_C_HEADER],[dnl
+#define _XOPEN_SOURCE 700
+#define _MINGW_THREADSAFE_IO
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <process.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],[dnl
+#include <time.h>
+])
+static int global_fd;
+static const char *letters = "abcdefghijklmnopqrstuvwxyz";
+
+static const char *completion_failure = "failed";
+static const char *completion_success = "completed successfully";
+
+static const char *completion_status;
+])# MINGW_AT_XFD_C_HEADER
+
+# MINGW_AT_CHECK_RUN_XFDREAD( MODE )
+# ----------------------------------
+# Verify operation of interleaved read() and pread() calls, at
+# differing offsets, to a single file descriptor; MODE is "Single",
+# in which case the read() and pread() calls are all sequentially
+# interleaved within the main program thread, or "Multi-thread", in
+# which case the read() calls are performed within the main thread,
+# while the interleaved pread() calls are dispersed among multiple
+# concurrently running threads.
+#
+m4_define([MINGW_AT_CHECK_RUN_XFDREAD],[AT_SETUP([$1])
+AT_KEYWORDS([xfd])MINGW_AT_DATA_CRLF([expout],[[$1: completed successfully
+]])MINGW_AT_CHECK_RUN([MINGW_AT_XFD_C_HEADER([$1])[[
+void pread_check( void ]]MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],dnl
+[[*args __attribute__((unused))]])[[ )
+# define PREAD_OFFSET (pread_offset %= pread_limit + 1)
+{ char buf[8]; int pread_offset = 2, pread_limit = strlen( letters ) - sizeof( buf );
+]]MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],dnl
+  [[[for( ;; )]]])[[
+  { if( pread( global_fd, buf, sizeof( buf ), PREAD_OFFSET ) < 0)
+      ]]MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],[[[_endthread()]]],dnl
+      [[[completion_status = completion_failure]]])[[;
+    if( strncmp( letters + pread_offset++, buf, sizeof( buf )) != 0 )
+      completion_status = completion_failure;
+  }
+}
+int main()
+{ char tmpname[] = "preadXXXXXX";
+  completion_status = completion_failure;
+
+  if( (global_fd = mkstemp( tmpname )) >= 0 )
+  { ssize_t maxlen = strlen( letters );
+    write( global_fd, letters, maxlen );
+    close( global_fd );
+
+    if( (global_fd = open( tmpname, O_RDONLY )) >= 0 )
+    { char buf[4]; int cycles; off_t pos;
+      ssize_t max_offset = maxlen - sizeof( buf ) + 1;
+      const char *status = (completion_status = completion_success);
+]]MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],[[[
+      struct timespec pause = { 0, 1500000L };
+      for( cycles = 0; cycles < 8; cycles++ )
+      { _beginthread( pread_check, 0, NULL );
+	nanosleep( &pause, NULL );
+      }
+]]])[[
+      for( cycles = 0; cycles < 100; cycles++ )
+      { if( (pos = lseek( global_fd, 0, SEEK_CUR )) >= max_offset )
+	  pos = lseek( global_fd, pos % max_offset, SEEK_SET );
+	read( global_fd, buf, sizeof( buf ) );
+	if( strncmp( letters + pos, buf, sizeof( buf )) != 0 )
+	  completion_status = completion_failure;
+]]MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],dnl
+	[[[nanosleep( &pause, NULL );]]],dnl
+	[[[pread_check();]]])[[
+      }
+      close( global_fd );
+    }
+    remove( tmpname );
+  }
+  printf( "$1: %s\n", completion_status );
+  return 0;
+}]]],,[expout])dnl
+AT_CLEANUP
+])# MINGW_AT_CHECK_RUN_XFDREAD
+
+# MINGW_AT_CHECK_RUN_XFDWRITE( MODE )
+# -----------------------------------
+# Verify operation of interleaved write() and pwrite() calls, at
+# differing offsets, to a single file descriptor; MODE is "Single",
+# in which case the write() and pwrite() calls are all sequentially
+# interleaved within the main program thread, or "Multi-thread", in
+# which case the write() calls are performed within the main thread,
+# while the interleaved pwrite() calls are dispersed among multiple
+# concurrently running threads.
+#
+m4_define([MINGW_AT_CHECK_RUN_XFDWRITE],[AT_SETUP([$1])
+AT_KEYWORDS([xfd])MINGW_AT_DATA_CRLF([expout],[[$1: completed successfully
+]])MINGW_AT_CHECK_RUN([MINGW_AT_XFD_C_HEADER([$1])[[
+#include <ctype.h>]]dnl
+MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],[[
+
+static struct timespec pause = { 0, 1500000L };]])[[
+
+void pwrite_char( void *p )
+{ int loop, maxlen = strlen( letters ), i = *(int *)(p);
+  char x[] = { letters[i], toupper( letters[i] ) };
+  for( loop = 1; loop < maxlen; loop++ )
+  { if( pwrite( global_fd, &x[loop % 2], 1, (off_t)(i) + loop * maxlen) != 1 )
+    { perror( "pwrite" ); completion_status = completion_failure; }]]dnl
+MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],[[
+    nanosleep( &pause, NULL );]])[[
+  }
+}
+
+int main()
+{ char tmpname[] = "pwriteXXXXXX";
+  completion_status = completion_success;
+
+  if( (global_fd = mkstemp( tmpname )) >= 0 )
+  { int i, j, maxlen = strlen( letters );]]dnl
+MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],[[
+    for( i = 0; i < maxlen; i++ )
+    { _beginthread( pwrite_char, 0, &i );
+      nanosleep( &pause, NULL );
+    }]])[[
+    for( i = 0; i < maxlen; i++ )
+    { if( write( global_fd, letters + i, 1 ) != 1 )
+      { perror( "write" ); completion_status = completion_failure; }]]dnl
+MINGW_AT_XFD_MULTITHREAD_IFELSE([$1],[[
+      nanosleep( &pause, NULL );]],[[
+      pwrite_char( &i );]])[[
+    }
+    lseek( global_fd, 0, SEEK_SET );
+    for( j = 0; j < maxlen; j++ )
+    { for( i = 0; i < maxlen; i++ )
+      { char x;
+	if( (read( global_fd, &x, 1 ) != 1) || (tolower( x ) != letters[i]) )
+	  completion_status = completion_failure;
+      }
+    }
+    close( global_fd );
+    remove( tmpname );
+  }
+  printf( "$1: %s\n", completion_status );
+  return 0;
+}]]],,[expout])dnl
+AT_CLEANUP
+])# MINGW_AT_CHECK_RUN_XFDWRITE
+
+# Run sequence of thread-safe I/O tests.
+#
+AT_BANNER([MinGW.org thread-safe I/O extensions.])
+MINGW_AT_CHECK_RUN_XFDREAD([Single thread: interleaved read()/pread()])
+MINGW_AT_CHECK_RUN_XFDWRITE([Single thread: interleaved write()/pwrite()])
+MINGW_AT_CHECK_RUN_XFDREAD([Multi-thread: interleaved read()/pread()])
+MINGW_AT_CHECK_RUN_XFDWRITE([Multi-thread: interleaved write()/pwrite()])
+
+# vim: filetype=config formatoptions=croql
+# $RCSfile$: end of file
