crt: add tests for C95 conversion functions

Signed-off-by: Kirill Makurin <maiddaisuki@outlook.com>
diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index 7e7fde5..97da616 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -4403,6 +4403,9 @@
   testcases/t_imagebase \
   testcases/t_lfs \
   testcases/t_matherr \
+  testcases/t_mbrlen \
+  testcases/t_mbrtowc \
+  testcases/t_mbsrtowcs \
   testcases/t_nullptrexception \
   testcases/t_readdir \
   testcases/t_snprintf \
@@ -4430,6 +4433,8 @@
   testcases/t_trycatch \
   testcases/t_stat_slash \
   testcases/t_vsscanf \
+  testcases/t_wcrtomb \
+  testcases/t_wcsrtombs \
   testcases/t_wreaddir \
   testcases/t_fseeko64
 
diff --git a/mingw-w64-crt/testcases/t_mbrlen.c b/mingw-w64-crt/testcases/t_mbrlen.c
new file mode 100644
index 0000000..0cf6b18
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_mbrlen.c
@@ -0,0 +1,139 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+char Ascii[] = {'a'};
+char NonAscii[] = {(char) 0x80};
+char Multibyte[] = {(char) 0x81, (char) 0x81};
+char InvalidMultibyte[] = {(char) 0x81, 0};
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  mbstate_t state = {0};
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes in range [0,255] are valid
+   */
+  for (unsigned char c = 0;; ++c) {
+    assert (mbrlen ((char *) &c, MB_CUR_MAX, &state) == !!c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0xFF) {
+      break;
+    }
+  }
+
+  /**
+   * Detect invalid conversion state
+   *
+   * NOTE: this is optional error condition specified in POSIX.
+   * This check fails with CRT's mbrlen.
+   */
+  state = Ascii[0];
+
+  assert (mbrlen ((char *) &Ascii, MB_CUR_MAX, &state) == (size_t) -1);
+  assert (!mbsinit (&state));
+  assert (errno == EINVAL);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Set conversion state to initial state
+   */
+
+  assert (mbrlen (NULL, 0, &state) == 0);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Test SBCS code page
+   */
+  assert (setlocale (LC_ALL, "English_United States.ACP") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes must be valid
+   */
+  for (unsigned char c = 0;; ++c) {
+    assert (mbrlen ((char *) &c, MB_CUR_MAX, &state) == !!c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0xFF) {
+      break;
+    }
+  }
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /**
+   * Make sure ASCII characters are handled correctly
+   */
+  for (char c = 0;; ++c) {
+    assert (mbrlen (&c, 1, &state) == !!c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0x7F) {
+      break;
+    }
+  }
+
+  /**
+   * Try convert incomplete multibyte character
+   */
+
+  assert (mbrlen ((char *) Multibyte, 1, &state) == (size_t) -2);
+  assert (!mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Complete multibyte character
+   *
+   * NOTE: return value does not conform to ISO C and POSIX.
+   * This behavior is implemented for consistency with CRT.
+   */
+
+  assert (mbrlen ((char *) Multibyte + 1, 1, &state) == 2);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert multibyte character
+   */
+
+  assert (mbrlen ((char *) Multibyte, MB_CUR_MAX, &state) == 2);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Invalid multibyte character
+   */
+
+  assert (mbrlen ((char *) InvalidMultibyte, MB_CUR_MAX, &state) == (size_t) -1);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_mbrtowc.c b/mingw-w64-crt/testcases/t_mbrtowc.c
new file mode 100644
index 0000000..fd2fcb7
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_mbrtowc.c
@@ -0,0 +1,164 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+char Ascii[] = {'a'};
+char NonAscii[] = {(char) 0x80};
+char Multibyte[] = {(char) 0x81, (char) 0x81};
+char InvalidMultibyte[] = {(char) 0x81, 0};
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  mbstate_t state = {0};
+  wchar_t   wc = WEOF;
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes in range [0,255] are valid and must convert to themselves
+   */
+  for (unsigned char c = 0;; ++c) {
+    assert (mbrtowc (&wc, (char *) &c, MB_CUR_MAX, &state) == !!c);
+    assert (wc == c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0xFF) {
+      break;
+    }
+  }
+
+  /**
+   * Detect invalid conversion state
+   *
+   * NOTE: this is optional error condition specified in POSIX.
+   * This check fails with CRT's mbrtowc.
+   */
+  state = Ascii[0];
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) &Ascii, MB_CUR_MAX, &state) == (size_t) -1);
+  assert (wc == WEOF);
+  assert (!mbsinit (&state));
+  assert (errno == EINVAL);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Set conversion state to initial state
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, NULL, 0, &state) == 0);
+  assert (wc == WEOF);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Test SBCS code page
+   * NOTE: Code page 28951 is ISO-8859-1
+   */
+  assert (setlocale (LC_ALL, "English_United States.28591") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes must be valid
+   *
+   * We test ISO-8859-1 so that all bytes must convert to themselves
+   */
+  for (unsigned char c = 0;; ++c) {
+    wc = WEOF;
+
+    assert (mbrtowc (&wc, (char *) &c, MB_CUR_MAX, &state) == !!c);
+    assert (wc == c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0xFF) {
+      break;
+    }
+  }
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /**
+   * Make sure ASCII characters are handled correctly
+   */
+  for (char c = 0;; ++c) {
+    wc = WEOF;
+
+    assert (mbrtowc (&wc, &c, 1, &state) == !!c);
+    assert (wc == c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0x7F) {
+      break;
+    }
+  }
+
+  /**
+   * Try convert incomplete multibyte character
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) Multibyte, 1, &state) == (size_t) -2);
+  /* This assertion fails with CRT's version */
+  assert (wc == WEOF);
+  assert (!mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Complete multibyte character
+   *
+   * NOTE: return value does not conform to ISO C and POSIX.
+   * This behavior is implemented for consistency with CRT.
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) Multibyte + 1, 1, &state) == 2);
+  assert (wc != WEOF);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert complete multibyte character
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) Multibyte, MB_CUR_MAX, &state) == 2);
+  assert (wc != WEOF);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Try convert invalid multibyte character
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) InvalidMultibyte, MB_CUR_MAX, &state) == (size_t) -1);
+  /* This assertion fails with CRT's version */
+  assert (wc == WEOF);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_mbsrtowcs.c b/mingw-w64-crt/testcases/t_mbsrtowcs.c
new file mode 100644
index 0000000..ba2b838
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_mbsrtowcs.c
@@ -0,0 +1,377 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+/* ASCII text */
+char          AsciiText[] = "Simple English string.";
+/* SBCS text (code page 1252) */
+unsigned char SBCSText[] = {'a', 'A', 0xC0, 0xE0, 'e', 'E', 0xC8, 0xE8, 0xCB, 0xEB, 0x0};
+/* DBCS text (code page 932) */
+unsigned char DBCSText[] = {0x93, 0xFA, 0x96, 0x7B, 0x8C, 0xEA, 0x83, 0x65, 0x83, 0x4E, 0x83, 0x58, 0x83, 0x67, 0x0};
+/* Mix of single-byte and double-byte characters */
+unsigned char MixedText[] = {0x93, 0xFA, 'n', 'i', 0x96, 0x7B, 'h', 'o', 'n', 0x8C, 0xEA, 'g', 'o', 0x0};
+/* DBCS text with truncated multibyte character */
+unsigned char BadText[] = {0x93, 0xFA, 0x96, 0x7B, 0x8C, 0x0};
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  mbstate_t state = {0};
+  wchar_t   buffer[BUFSIZ];
+
+  const char *original_text = NULL;
+  const char *text = NULL;
+  size_t      text_length = 0;
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = sizeof AsciiText - 1;
+
+  /**
+   * Get length of converted AsciiString
+   *
+   * - return value must be `text_length`
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert AsciiString
+   *
+   * - return value must be `text_length`
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == L'\0');
+
+  /**
+   * Convert 10 characters of AsciiString
+   *
+   * - return value must be 10
+   * - value of `text` must be `original_text + 10`
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must not be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == WEOF);
+
+  /* Test SBCS input */
+
+  original_text = (char *) SBCSText;
+  text_length = sizeof SBCSText - 1;
+
+  /**
+   * Get length of converted SBCSText
+   *
+   * - return value must be `text_length`
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert SBCSText
+   *
+   * - return value must be `text_length`
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == L'\0');
+
+  /**
+   * Test SBCS code page
+   */
+  assert (setlocale (LC_ALL, "English_United States.ACP") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /* Test SBCS input */
+
+  original_text = (char *) SBCSText;
+  text_length = sizeof SBCSText - 1;
+
+  /**
+   * Get length of converted SBCSText
+   *
+   * - return value must be length of `text_length`
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert SBCSText
+   *
+   * - return value must be `text_length`
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == L'\0');
+
+  /**
+   * Convert 8 characters in SBCSText
+   *
+   * - return value must be 8
+   * - value of `text` must be `original_text + 8`
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must not be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, 8, &state) == 8);
+  assert (text == original_text + 8);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[8] == WEOF);
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = sizeof AsciiText - 1;
+
+  /**
+   * Convert AsciiString
+   *
+   * - return value must be `text_length`
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == L'\0');
+
+  /* Test DBCS input */
+
+  original_text = (char *) DBCSText;
+  text_length = sizeof DBCSText - 1;
+
+  /**
+   * Get length of converted DBCSText
+   *
+   * - return value must be 7
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == 7);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert 3 multibyte characters in DBCSText
+   *
+   * - return value must be 3
+   * - value of `text` must point to `original_text + 6`
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must not be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, 3, &state) == 3);
+  assert (text == original_text + 6);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[0] != WEOF && buffer[1] != WEOF && buffer[2] != WEOF && buffer[3] == WEOF);
+
+  /**
+   * Convert DBCSText
+   *
+   * - return value must be 7
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == 7);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[7] == L'\0');
+
+  /* Test mixed input */
+
+  original_text = (char *) MixedText;
+
+  /**
+   * Get length of converted MixedText
+   *
+   * - return value must be 10
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == 10);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Converted MixedText
+   *
+   * - return value must be 10
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == 10);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == L'\0');
+
+  /**
+   * Converted 7 multibyte characters in MixedText
+   *
+   * - return value must be 7
+   * - value of `text` must be `original_text + 9`
+   * - `state` must be in the initial state
+   * - value of `errno` must not change
+   * - converted string must not be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, 7, &state) == 7);
+  assert (text == original_text + 9);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[7] == WEOF);
+
+  /* Test bad DBCS input */
+
+  original_text = (char *) BadText;
+
+  /**
+   * Try get length of converted BadText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - value of `errno` must be EILSEQ
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Try convert BadText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must be `original_text + 4`
+   * - `state` must be in the initial state
+   * - value of `errno` must be EILSEQ
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == (size_t) -1);
+  assert (text == original_text + 4);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+  /* This assertion fails with CRT's version */
+  assert (buffer[0] != WEOF && buffer[1] != WEOF && buffer[2] == WEOF);
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_wcrtomb.c b/mingw-w64-crt/testcases/t_wcrtomb.c
new file mode 100644
index 0000000..76d8ca0
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_wcrtomb.c
@@ -0,0 +1,199 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  mbstate_t state = {0};
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All values in range [0,255] are valid and must convert to themselves
+   */
+  for (wchar_t wc = 0; wc < 0x100; ++wc) {
+    char c = EOF;
+
+    assert (wcrtomb (&c, wc, &state) == 1);
+    assert ((unsigned char) c == wc);
+    assert (errno == 0);
+    assert (mbsinit (&state));
+  }
+
+  /**
+   * Detect invalid conversion state
+   *
+   * NOTE: this is optional error condition specified in POSIX.
+   * This check fails with CRT's wcrtomb.
+   */
+  state = 1;
+
+  if (1) {
+    char c = EOF;
+
+    assert (wcrtomb (&c, L'\0', &state) == (size_t) -1);
+    assert (c == EOF);
+    assert (errno == EINVAL);
+    assert (!mbsinit (&state));
+
+    // reset errno
+    _set_errno (0);
+  }
+
+  /**
+   * Set conversion state to initial state
+   */
+
+  assert (wcrtomb (NULL, WEOF, &state) == 1);
+  assert (errno == 0);
+  assert (mbsinit (&state));
+
+  /**
+   * Try convert character which cannot be repesented by a single byte
+   */
+  if (1) {
+    char buffer = EOF;
+
+    assert (wcrtomb (&buffer, L'語', &state) == (size_t) -1);
+    assert (buffer == EOF);
+    assert (errno == EILSEQ);
+    assert (mbsinit (&state));
+
+    // reset errno
+    _set_errno (0);
+  }
+
+  /**
+   * Try to convert low and high surrogates
+   */
+  for (wchar_t wc = 0;; ++wc) {
+    if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc)) {
+      char c = EOF;
+
+      assert (wcrtomb (&c, wc, &state) == (size_t) -1);
+      assert (c == EOF);
+      assert (errno = EILSEQ);
+      assert (mbsinit (&state));
+
+      // reset errno
+      _set_errno (0);
+    }
+
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  /**
+   * Test SBCS code page
+   * NOTE: Code page 28951 is ISO-8859-1
+   */
+  assert (setlocale (LC_ALL, "English_United States.28591") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All values in range [0,255] must convert to themselves
+   */
+  for (wchar_t wc = 0; wc < 0x100; ++wc) {
+    char c = EOF;
+
+    assert (wcrtomb (&c, wc, &state) == 1);
+    assert ((unsigned char) c == wc);
+    assert (errno == 0);
+    assert (mbsinit (&state));
+  }
+
+  /**
+   * Try to convert low and high surrogates
+   */
+  for (wchar_t wc = 0;; ++wc) {
+    if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc)) {
+      char c = EOF;
+
+      assert (wcrtomb (&c, wc, &state) == (size_t) -1);
+      /* This assertion fails with CRT's version */
+      assert (c == EOF);
+      assert (errno = EILSEQ);
+      assert (mbsinit (&state));
+
+      // reset errno
+      _set_errno (0);
+    }
+
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /**
+   * All values in range [0,127] are valid ASCII characters
+   */
+  for (wchar_t wc = 0; wc < 0x80; ++wc) {
+    char c = EOF;
+
+    assert (wcrtomb (&c, wc, &state) == 1);
+    assert ((unsigned char) c == wc);
+    assert (errno == 0);
+    assert (mbsinit (&state));
+  }
+
+  /**
+   * Try convert multibyte characters
+   */
+  wchar_t DBCS[] = {L'日', L'本', L'語', L'。'};
+
+  for (size_t i = 0; i < _countof (DBCS); ++i) {
+    char buffer[2] = {0};
+
+    assert (wcrtomb (buffer, DBCS[i], &state) == 2);
+    assert (buffer[0] != 0 && buffer[1] != 0);
+    assert (errno == 0);
+    assert (mbsinit (&state));
+  }
+
+  /**
+   * Try to convert low and high surrogates
+   */
+  for (wchar_t wc = 0;; ++wc) {
+    if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc)) {
+      char c = EOF;
+
+      assert (wcrtomb (&c, wc, &state) == (size_t) -1);
+      /* This assertion fails with CRT's version */
+      assert (c == EOF);
+      assert (errno = EILSEQ);
+      assert (mbsinit (&state));
+
+      // reset errno
+      _set_errno (0);
+    }
+
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_wcsrtombs.c b/mingw-w64-crt/testcases/t_wcsrtombs.c
new file mode 100644
index 0000000..f2c660d
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_wcsrtombs.c
@@ -0,0 +1,570 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+wchar_t AsciiText[] = L"Simple English text.";
+wchar_t SBCSText[] = L"Sömè fÛnnÿ têxt";
+wchar_t DBCSText[] = L"日本語テクスト";
+wchar_t MixedText[] = L"日NI本HON語GO";
+wchar_t BadText[] = {L'テ', L'く', WEOF, L'ト'};
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  const wchar_t *original_text = NULL;
+  const wchar_t *text = NULL;
+  size_t         text_length = 0;
+
+  char      buffer[BUFSIZ];
+  mbstate_t state = {0};
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = _countof (AsciiText) - 1;
+
+  /**
+   * Get length of converted AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in AsciiText
+   *
+   * - return value must be 10
+   * - value of `test` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test SBCS input */
+
+  original_text = SBCSText;
+  text_length = _countof (SBCSText) - 1;
+
+  /**
+   * Get length of converted SBCSText
+   *
+   * - return value must be 15
+   * - value of `text` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert SBCSText
+   *
+   * - return value must be 15
+   * - value of `text` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in SBCSText
+   *
+   * - return value must be 10
+   * - value of `text` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test DBCS input */
+
+  original_text = DBCSText;
+  text_length = _countof (DBCSText) - 1;
+
+  /**
+   * Try get length of converted DBCSText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+  assert (buffer[0] == EOF);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Try convert DBCSText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Test SBCS code page
+   */
+  assert (setlocale (LC_ALL, "English_United States.ACP") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = _countof (AsciiText) - 1;
+
+  /**
+   * Get length of converted AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in AsciiText
+   *
+   * - return value must be 10
+   * - value of `test` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test SBCS input */
+
+  original_text = SBCSText;
+  text_length = _countof (SBCSText) - 1;
+
+  /**
+   * Get length of converted SBCSText
+   *
+   * - return value must be 15
+   * - value of `text` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert SBCSText
+   *
+   * - return value must be 15
+   * - value of `text` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in SBCSText
+   *
+   * - return value must be 10
+   * - value of `text` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test DBCS input */
+
+  original_text = DBCSText;
+  text_length = _countof (DBCSText) - 1;
+
+  /**
+   * Try get length of converted DBCSText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Try convert DBCSText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   * - nothing must be written to `buffer`
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+  /* This assertion fails with CRT's version */
+  assert (buffer[0] == EOF);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = _countof (AsciiText) - 1;
+
+  /**
+   * Get length of converted AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in AsciiText
+   *
+   * - return value must be 10
+   * - value of `test` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test DBCS input */
+
+  original_text = DBCSText;
+  text_length = _countof (DBCSText) - 1;
+
+  /**
+   * Get length of converted DBCSText
+   *
+   * - return value must be 14
+   * - value of `text` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == 14);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert DBCSText
+   *
+   * - return value must be 14
+   * - value of `text` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == 14);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[14] == '\0');
+
+  /**
+   * Convert 5 wide characters in DBCSText
+   *
+   * - return value must be 10
+   * - value of `text` must be `original_text + 5`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 11, &state) == 10);
+  assert (text == original_text + 5);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test mixed input */
+
+  original_text = MixedText;
+  text_length = _countof (MixedText) - 1;
+
+  /**
+   * Get length of converted MixedText
+   *
+   * - return value must be 13
+   * - value of `test` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == 13);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert MixedText
+   *
+   * - return value must be 13
+   * - value of `test` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be teminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == 13);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[13] == '\0');
+
+  /**
+   * Convert 7 wide characters in MixedText
+   *
+   * - return value must be 9
+   * - value of `test` must be `original_text + 7`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be teminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 9);
+  assert (text == original_text + 7);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[9] == EOF);
+
+  /* Test bad input */
+
+  original_text = BadText;
+  text_length = _countof (BadText) - 1;
+
+  /**
+   * Try get length of converted BadText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  /**
+   * Try convert BadText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must be `original_text + 2`
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == (size_t) -1);
+  assert (text == original_text + 2);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+  /* This assertion fails with CRT's version */
+  assert (buffer[3] != EOF && buffer[4] == EOF);
+
+  // reset errno
+  _set_errno (0);
+
+  return 0;
+}