From: Eric Pouech epouech@codeweavers.com
Signed-off-by: Eric Pouech epouech@codeweavers.com --- dlls/msvcrt/tests/file.c | 271 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+)
diff --git a/dlls/msvcrt/tests/file.c b/dlls/msvcrt/tests/file.c index 1ac6cfe3182..81d0c34a397 100644 --- a/dlls/msvcrt/tests/file.c +++ b/dlls/msvcrt/tests/file.c @@ -3059,6 +3059,274 @@ static void test_ioinfo_flags(void) free(tempf); }
+static unsigned __cdecl winetest_log(const char *format, ...) +{ + const char *logname = "file.log"; + va_list args; + FILE *log; + unsigned ret = -1; + + if (!strcmp( format, "delete" )) + return DeleteFileA(logname); + if (!strcmp( format, "trace" )) + { + if ((log = fopen( logname, "r" ))) + { + char tmp[256]; + + while (fgets( tmp, ARRAY_SIZE(tmp), log )) + trace( "%s", tmp ); + fclose( log ); + return 1; + } + return 0; + } + va_start( args, format ); + if ((log = fopen( logname, "a" ))) + { + ret = vfprintf( log, format, args ); + fclose( log ); + } + va_end( args ); + return ret; +} + +static void create_file(const char *filename, const char *content) +{ + DWORD ret; + HANDLE file = CreateFileA( filename, GENERIC_WRITE, + FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL ); + ok( file != INVALID_HANDLE_VALUE, "Couldn't create file\n" ); + if (content) + { + DWORD nw; + ret = WriteFile( file, content, strlen( content ), &nw, NULL ); + ok( ret && nw == strlen( content ), "Couldn't write file\n" ); + } + CloseHandle( file ); +} + +enum buffer_state +{ + BS_NO_BUFFER, + BS_CHAR_BUFFER, + BS_FULL_BUFFER, + BS_ERROR, +}; + +static enum buffer_state get_buffer_state(FILE *f) +{ + unsigned bufsize = 0xffffffff; + char *base = NULL; + char *charbuf = (char*)(f - 1); /* so that it's not inside f structure */ + + bufsize = f->_bufsiz; + base = f->_base; + charbuf = (char*)&f->_charbuf; + + /* we could expect bufsize to be 0, but it's sometimes 2 */ + if (base == NULL && bufsize != 4096) return BS_NO_BUFFER; + if (base == charbuf && bufsize == 2) return BS_CHAR_BUFFER; + if (base != NULL && bufsize == 4096) return BS_FULL_BUFFER; + winetest_log( "Can't guess buffer state: bufsize=%04u base=%p\n", bufsize, base ); + + return BS_ERROR; +} + +static BOOL check_buffer_state_discovery(void) +{ + static char file_test_buffer[4096]; + unsigned cond = 0; + FILE *file_test = fopen( "foo.tmp", "w" ); + errno_t err; + + if (!file_test) return FALSE; + + winetest_log("delete"); + + if (get_buffer_state( file_test ) == BS_NO_BUFFER) cond++; + err = setvbuf( file_test, NULL, _IONBF, 0 ); + ok( err == 0, "setvbuf failed\n" ); + if (get_buffer_state( file_test ) == BS_CHAR_BUFFER) cond++; + err = setvbuf( file_test, file_test_buffer, _IOFBF, sizeof(file_test_buffer) ); + ok( err == 0, "setvbuf failed\n" ); + if (get_buffer_state( file_test ) == BS_FULL_BUFFER) cond++; + err = setvbuf( file_test, file_test_buffer, _IOLBF, sizeof(file_test_buffer) ); + ok( err == 0, "setvbuf failed\n" ); + if (get_buffer_state( file_test ) == BS_FULL_BUFFER) cond++; + fclose( file_test ); + + if (cond != 4) + { + win_skip("Incompatible Windows platform, skipping test\n"); + trace("DLL2 ucrt=%u msvcrt=%u wine=%u\n", + GetModuleHandleA("ucrtbase.dll") != NULL, + GetModuleHandleA("msvcrt.dll") != NULL, + winetest_platform_is_wine); + winetest_log("trace"); + return FALSE; + } + return TRUE; +} + +/* We could test more types: + * - pipes: + * - console: we assume that NUL and console behave similary (they are of FILE_TYPE_CHAR) + */ +enum handle_creation {H_FILE, H_NUL, H_REOPEN = 0x10, H_KIND_MASK = 0x0F}; + +static void test_std_buffer_state(const char *selfname) +{ + static struct + { + /* input */ + unsigned type; + /* output */ + enum buffer_state std_buffer_state[3]; + BOOL with_todo; + } + test_file_type[] = + { + /* inheriting from parent */ + /* Note: wine/test.h calls set_vbuf(_IONBF) (even in child) + * hence the result BS_CHAR_BUFFER for stdout without reopen + */ + {H_FILE, {BS_FULL_BUFFER, BS_CHAR_BUFFER, BS_FULL_BUFFER}}, + {H_NUL, {BS_FULL_BUFFER, BS_CHAR_BUFFER, BS_NO_BUFFER}}, + /* reopen in child */ + {H_FILE | H_REOPEN, {BS_FULL_BUFFER, BS_FULL_BUFFER, BS_FULL_BUFFER}}, + {H_NUL | H_REOPEN, {BS_FULL_BUFFER, BS_NO_BUFFER, BS_NO_BUFFER}}, + }; + char cmdline[MAX_PATH]; + SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; + STARTUPINFOA startup; + PROCESS_INFORMATION proc; + DWORD exit_code, expected_exit_code; + unsigned i; + BOOL ret; + + /* check that get_buffer_state is coherent */ + if (!check_buffer_state_discovery()) return; + + for (i = 0; i < ARRAY_SIZE(test_file_type); i++) + { + winetest_push_context( "stdbuf[%d]", i ); + memset( &startup, 0, sizeof(startup) ); + startup.cb = sizeof(startup); + + winetest_log("delete"); + + switch ((int)test_file_type[i].type) + { + case H_FILE: + /* so that we can read from stdin */ + create_file( "file0.tmp", "first content\r\n" ); + startup.hStdInput = CreateFileA( "file0.tmp", GENERIC_READ, + FILE_SHARE_READ, &sa, OPEN_EXISTING, 0, NULL ); + startup.hStdOutput = CreateFileA( "file1.tmp", GENERIC_WRITE, + FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, 0, NULL ); + startup.hStdError = CreateFileA( "file2.tmp", GENERIC_WRITE, + FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, 0, NULL ); + startup.dwFlags = STARTF_USESTDHANDLES; + break; + case H_NUL: + startup.hStdInput = CreateFileA( "NUL", GENERIC_READ, + FILE_SHARE_READ, &sa, OPEN_EXISTING, 0, NULL ); + startup.hStdOutput = CreateFileA( "NUL", GENERIC_WRITE, + FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, 0, NULL ); + startup.hStdError = CreateFileA( "NUL", GENERIC_WRITE, + FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, 0, NULL ); + startup.dwFlags = STARTF_USESTDHANDLES; + break; + case H_FILE | H_REOPEN: + create_file( "file0.tmp", "first content\r\n" ); + break; + case H_NUL | H_REOPEN: + break; + default: ok(0, "\n"); break; + } + + sprintf( cmdline, "%s file stdbuf %#x", selfname, test_file_type[i].type ); + ret = CreateProcessA( NULL, cmdline, NULL, NULL, (startup.dwFlags & STARTF_USESTDHANDLES) != 0, 0, NULL, NULL, &startup, &proc ); + ok(ret, "Couldn't create child process '%s' %p %p %p: %lu\n", cmdline, startup.hStdInput, startup.hStdOutput, startup.hStdError, GetLastError()); + ret = WaitForSingleObject( proc.hProcess, 30000 ); + ok(ret == WAIT_OBJECT_0, "Unexpected process termination\n"); + ret = GetExitCodeProcess( proc.hProcess, &exit_code ); + ok( ret, "Unexpected result\n" ); + expected_exit_code = test_file_type[i].std_buffer_state[0] << 0 | + test_file_type[i].std_buffer_state[1] << 2 | + test_file_type[i].std_buffer_state[2] << 4; + todo_wine_if( test_file_type[i].with_todo ) + ok( exit_code == expected_exit_code, "Unexpected buffer state %lx: stdin=%lx/%x, stdout=%lx/%x, stderr=%lx/%x\n", + exit_code >> 6, + (exit_code >> 0) & 3, test_file_type[i].std_buffer_state[0], + (exit_code >> 2) & 3, test_file_type[i].std_buffer_state[1], + (exit_code >> 4) & 3, test_file_type[i].std_buffer_state[2] ); + + CloseHandle( proc.hProcess ); + CloseHandle( proc.hThread ); + + winetest_log("trace"); + + switch ((int)test_file_type[i].type) + { + case H_FILE: + case H_NUL: + CloseHandle( startup.hStdInput ); + CloseHandle( startup.hStdOutput ); + CloseHandle( startup.hStdError ); + break; + default: + break; + } + DeleteFileA( "file0.tmp" ); + DeleteFileA( "file1.tmp" ); + DeleteFileA( "file2.tmp" ); + + winetest_pop_context(); + } +} + +static void test_std_buffer_state_child(int argc, char **argv) +{ + unsigned flags = 0; + unsigned ret = 0; + char ch; + + if (argc >= 4) flags = strtol( argv[3], NULL, 0 ); + + if (flags & H_REOPEN) + { + switch (flags & H_KIND_MASK) + { + case H_FILE: + if (!freopen( "file0.tmp", "r", stdin )) ret |= 1 << 6; + if (!freopen( "file1.tmp", "w", stdout )) ret |= 1 << 6; + if (!freopen( "file2.tmp", "w", stderr )) ret |= 1 << 6; + break; + case H_NUL: + if (!freopen( "NUL", "r", stdin )) ret |= 1 << 6; + if (!freopen( "NUL", "w", stdout )) ret |= 1 << 6; + if (!freopen( "NUL", "w", stderr )) ret |= 1 << 6; + break; + default: + ret |= 1 << 6; + break; + } + } + + /* these operations shall force buffer creation (if required) */ + fscanf( stdin, "%c", &ch ); + fprintf( stdout, "f" ); + fprintf( stderr, "f" ); + + ret |= get_buffer_state( stdin ) << 0; + ret |= get_buffer_state( stdout ) << 2; + ret |= get_buffer_state( stderr ) << 4; + + ExitProcess( ret ); +} + START_TEST(file) { int arg_c; @@ -3079,6 +3347,8 @@ START_TEST(file) test_pipes_child(arg_c, arg_v); else if (strcmp(arg_v[2], "stdin") == 0) test_invalid_stdin_child(); + else if (strcmp(arg_v[2], "stdbuf") == 0) + test_std_buffer_state_child(arg_c, arg_v); else ok(0, "invalid argument '%s'\n", arg_v[2]); return; @@ -3135,6 +3405,7 @@ START_TEST(file) test_fopen_hints(); test_open_hints(); test_ioinfo_flags(); + test_std_buffer_state(arg_v[0]);
/* Wait for the (_P_NOWAIT) spawned processes to finish to make sure the report * file contains lines in the correct order