Basic conformance tests create test source directories with files and sub folders, execute robocopy on it, checking whether the resulting destination directory, the remaining source directory and the exit code is as expected
Signed-off-by: Florian Eder others.meder@gmail.com --- v7: Added conformance tests for the behaiviour added in the following patches, merged patch 2 and 6 from v6 --- configure | 1 + configure.ac | 1 + programs/robocopy/tests/Makefile.in | 4 + programs/robocopy/tests/robocopy.c | 411 ++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+) create mode 100644 programs/robocopy/tests/Makefile.in create mode 100644 programs/robocopy/tests/robocopy.c
diff --git a/configure b/configure index fd62dd6e078..49e43ebebdb 100755 --- a/configure +++ b/configure @@ -20990,6 +20990,7 @@ wine_fn_config_makefile programs/regini enable_regini wine_fn_config_makefile programs/regsvcs enable_regsvcs wine_fn_config_makefile programs/regsvr32 enable_regsvr32 wine_fn_config_makefile programs/robocopy enable_robocopy +wine_fn_config_makefile programs/robocopy/tests enable_tests wine_fn_config_makefile programs/rpcss enable_rpcss wine_fn_config_makefile programs/rundll.exe16 enable_win16 wine_fn_config_makefile programs/rundll32 enable_rundll32 diff --git a/configure.ac b/configure.ac index 22d3cd10eeb..fa7aae50f66 100644 --- a/configure.ac +++ b/configure.ac @@ -3873,6 +3873,7 @@ WINE_CONFIG_MAKEFILE(programs/regini) WINE_CONFIG_MAKEFILE(programs/regsvcs) WINE_CONFIG_MAKEFILE(programs/regsvr32) WINE_CONFIG_MAKEFILE(programs/robocopy) +WINE_CONFIG_MAKEFILE(programs/robocopy/tests) WINE_CONFIG_MAKEFILE(programs/rpcss) WINE_CONFIG_MAKEFILE(programs/rundll.exe16,enable_win16) WINE_CONFIG_MAKEFILE(programs/rundll32) diff --git a/programs/robocopy/tests/Makefile.in b/programs/robocopy/tests/Makefile.in new file mode 100644 index 00000000000..d86e70f4c43 --- /dev/null +++ b/programs/robocopy/tests/Makefile.in @@ -0,0 +1,4 @@ +TESTDLL = robocopy.exe + +C_SRCS = \ + robocopy.c diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c new file mode 100644 index 00000000000..76279f476f9 --- /dev/null +++ b/programs/robocopy/tests/robocopy.c @@ -0,0 +1,411 @@ +/* + * Copyright 2021 Florian Eder + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <wchar.h> +#include <wine/test.h> + +WCHAR temp_path[MAX_PATH]; + +static BOOL run_cmd(const WCHAR *cmd, DWORD *exit_code) +{ + STARTUPINFOW startup_info; + PROCESS_INFORMATION process_info; + WCHAR cmd_copy[256]; + + memset(&startup_info, 0, sizeof(STARTUPINFOW)); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = INVALID_HANDLE_VALUE; + startup_info.hStdOutput = INVALID_HANDLE_VALUE; + startup_info.hStdError = INVALID_HANDLE_VALUE; + + swprintf(cmd_copy, ARRAY_SIZE(cmd_copy), L"%s", cmd); + + if (!CreateProcessW(NULL, cmd_copy, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info)) + return FALSE; + + if (WaitForSingleObject(process_info.hProcess, 30000) == WAIT_TIMEOUT) + return FALSE; + + GetExitCodeProcess(process_info.hProcess, exit_code); + + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + + return TRUE; +} + +static void create_test_file(const WCHAR *relative_path, size_t size) +{ + HANDLE handle; + WCHAR path[MAX_PATH]; + swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path); + handle = CreateFileW(path, FILE_GENERIC_WRITE | FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL); + ok(handle != INVALID_HANDLE_VALUE, "creation of %s failed (0x%08x)\n", debugstr_w(path), GetLastError()); + if (size != 0) + { + BYTE *data; + DWORD bytes_written; + data = calloc(size, sizeof(BYTE)); + ok(WriteFile(handle, data, size, &bytes_written, NULL), "writing to %s failed (%d)\n", debugstr_w(path), GetLastError()); + } + CloseHandle(handle); +} + +static void create_test_folder(const WCHAR *relative_path) +{ + WCHAR path[MAX_PATH]; + swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path); + + CreateDirectoryW(path, NULL); +} + +static void create_test_source_folder(void) +{ + create_test_folder(L"source"); + create_test_folder(L"source\folderA"); + create_test_folder(L"source\folderB"); + create_test_folder(L"source\folderC"); + create_test_folder(L"source\folderA\folderD"); + create_test_folder(L"source\folderA\folderE"); + create_test_file(L"source\fileA.a", 4000); + create_test_file(L"source\fileB.b", 8000); + create_test_file(L"source\folderA\fileC.c", 60); + create_test_file(L"source\folderA\fileD.d", 80); + create_test_file(L"source\folderA\folderD\fileE.e", 10000); + create_test_file(L"source\folderB\fileF.f", 10000); + create_test_file(L"source\folderB\fileG.g", 200); +} + +static void check_file(const WCHAR *relative_path, BOOL should_exist) +{ + WCHAR path[MAX_PATH]; + swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path); + + ok (DeleteFileW(path) == should_exist, "file %s expected exist to be %d, but is %d\n", debugstr_w(relative_path), should_exist, !should_exist); + if (!should_exist) + ok(GetLastError() == ERROR_FILE_NOT_FOUND || GetLastError() == ERROR_PATH_NOT_FOUND, + "file %s DeleteFileW returned error %d, should not exist\n", + debugstr_w(relative_path), + GetLastError()); +} + +static void check_folder(const WCHAR *relative_path, BOOL should_exist) +{ + WCHAR path[MAX_PATH]; + swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path); + + ok (RemoveDirectoryW(path) == should_exist, "folder %s expected exist to be %d, but is %d\n", debugstr_w(relative_path), should_exist, !should_exist); + if (!should_exist) + ok(GetLastError() == ERROR_FILE_NOT_FOUND || GetLastError() == ERROR_PATH_NOT_FOUND, + "folder %s RemoveDirectoryW returned error %d, should not exist\n", + debugstr_w(relative_path), + GetLastError()); +} + +static void check_basic_copy_1_test(void) +{ + check_file(L"source\fileA.a", TRUE); + check_file(L"source\fileB.b", TRUE); + check_file(L"source\folderA\fileC.c", TRUE); + check_file(L"source\folderA\fileD.d", TRUE); + check_file(L"source\folderA\folderD\fileE.e", TRUE); + check_file(L"source\folderB\fileF.f", TRUE); + check_file(L"source\folderB\fileG.g", TRUE); + check_folder(L"source\folderA\folderD", TRUE); + check_folder(L"source\folderA\folderE", TRUE); + check_folder(L"source\folderA", TRUE); + check_folder(L"source\folderB", TRUE); + check_folder(L"source\folderC", TRUE); + check_folder(L"source", TRUE); + + todo_wine check_file(L"destination\fileA.a", TRUE); + todo_wine check_file(L"destination\fileB.b", TRUE); + check_file(L"destination\folderA\fileC.c", FALSE); + check_file(L"destination\folderA\fileD.d", FALSE); + check_file(L"destination\folderA\folderD\fileE.e", FALSE); + check_file(L"destination\folderB\fileF.f", FALSE); + check_file(L"destination\folderB\fileG.g", FALSE); + check_folder(L"destination\folderA\folderD", FALSE); + check_folder(L"destination\folderA\folderE", FALSE); + check_folder(L"destination\folderA", FALSE); + check_folder(L"destination\folderB", FALSE); + check_folder(L"destination\folderC", FALSE); + todo_wine check_folder(L"destination", TRUE); +} + +static void check_basic_copy_2_test(void) +{ + check_file(L"source\fileA.a", TRUE); + check_file(L"source\fileB.b", TRUE); + check_file(L"source\folderA\fileC.c", TRUE); + check_file(L"source\folderA\fileD.d", TRUE); + check_file(L"source\folderA\folderD\fileE.e", TRUE); + check_file(L"source\folderB\fileF.f", TRUE); + check_file(L"source\folderB\fileG.g", TRUE); + check_folder(L"source\folderA\folderD", TRUE); + check_folder(L"source\folderA\folderE", TRUE); + check_folder(L"source\folderA", TRUE); + check_folder(L"source\folderB", TRUE); + check_folder(L"source\folderC", TRUE); + check_folder(L"source", TRUE); + + check_file(L"destination\fileA.a", TRUE); + todo_wine check_file(L"destination\fileB.b", TRUE); + check_file(L"destination\folderA\fileC.c", FALSE); + check_file(L"destination\folderA\fileD.d", FALSE); + check_file(L"destination\folderA\folderD\fileE.e", FALSE); + check_file(L"destination\folderB\fileF.f", FALSE); + check_file(L"destination\folderB\fileG.g", FALSE); + check_folder(L"destination\folderA\folderD", FALSE); + check_folder(L"destination\folderA\folderE", FALSE); + check_folder(L"destination\folderA", FALSE); + check_folder(L"destination\folderB", FALSE); + check_folder(L"destination\folderC", FALSE); + check_folder(L"destination", TRUE); +} + +static void check_no_copy_test(void) +{ + check_file(L"source\fileA.a", TRUE); + check_file(L"source\fileB.b", TRUE); + check_file(L"source\folderA\fileC.c", TRUE); + check_file(L"source\folderA\fileD.d", TRUE); + check_file(L"source\folderA\folderD\fileE.e", TRUE); + check_file(L"source\folderB\fileF.f", TRUE); + check_file(L"source\folderB\fileG.g", TRUE); + check_folder(L"source\folderA\folderD", TRUE); + check_folder(L"source\folderA\folderE", TRUE); + check_folder(L"source\folderA", TRUE); + check_folder(L"source\folderB", TRUE); + check_folder(L"source\folderC", TRUE); + check_folder(L"source", TRUE); + + check_file(L"destination\fileA.a", FALSE); + check_file(L"destination\fileB.b", FALSE); + check_file(L"destination\folderA\fileC.c", FALSE); + check_file(L"destination\folderA\fileD.d", FALSE); + check_file(L"destination\folderA\folderD\fileE.e", FALSE); + check_file(L"destination\folderB\fileF.f", FALSE); + check_file(L"destination\folderB\fileG.g", FALSE); + check_folder(L"destination\folderA\folderD", FALSE); + check_folder(L"destination\folderA\folderE", FALSE); + check_folder(L"destination\folderA", FALSE); + check_folder(L"destination\folderB", FALSE); + check_folder(L"destination\folderC", FALSE); + check_folder(L"destination", FALSE); +} + +static void check_wildcard_1_test(void) +{ + check_file(L"source\fileA.a", TRUE); + check_file(L"source\fileB.b", TRUE); + check_file(L"source\folderA\fileC.c", TRUE); + check_file(L"source\folderA\fileD.d", TRUE); + check_file(L"source\folderA\folderD\fileE.e", TRUE); + check_file(L"source\folderB\fileF.f", TRUE); + check_file(L"source\folderB\fileG.g", TRUE); + check_folder(L"source\folderA\folderD", TRUE); + check_folder(L"source\folderA\folderE", TRUE); + check_folder(L"source\folderA", TRUE); + check_folder(L"source\folderB", TRUE); + check_folder(L"source\folderC", TRUE); + check_folder(L"source", TRUE); + + todo_wine check_file(L"destination\fileA.a", TRUE); + check_file(L"destination\fileB.b", FALSE); + check_file(L"destination\folderA\fileC.c", FALSE); + check_file(L"destination\folderA\fileD.d", FALSE); + check_file(L"destination\folderA\folderD\fileE.e", FALSE); + check_file(L"destination\folderB\fileF.f", FALSE); + check_file(L"destination\folderB\fileG.g", FALSE); + check_folder(L"destination\folderA\folderD", FALSE); + check_folder(L"destination\folderA\folderE", FALSE); + check_folder(L"destination\folderA", FALSE); + check_folder(L"destination\folderB", FALSE); + check_folder(L"destination\folderC", FALSE); + todo_wine check_folder(L"destination", TRUE); +} + +START_TEST(robocopy) +{ + DWORD exit_code; + WCHAR temp_cmd[512]; + int i; + const WCHAR *invalid_syntax_tests[] = + { + L"robocopy", + L"robocopy invalid_folder", + L"robocopy -flag invalid_folder", + L"robocopy invalid_folder destination", + L"robocopy -?", + L"robocopy invalid_folder -?", + L"robocopy invalid_folder /?", + }; + + /* robocopy is only available from Vista onwards, abort test if not available */ + if (!run_cmd(L"robocopy", &exit_code)) return; + + ok(GetTempPathW(ARRAY_SIZE(temp_path), temp_path) != 0, "couldn't get temp folder path\n"); + wcscat(temp_path, L"robocopy_test\"); + ok(CreateDirectoryW(temp_path, NULL), "couldn't create temp test folder %s, error %d\n", debugstr_w(temp_path), GetLastError()); + ok(SetCurrentDirectoryW(temp_path), "couldn't set CWD to temp folder %s\n", debugstr_w(temp_path)); + + for (i = 0; i < ARRAY_SIZE(invalid_syntax_tests); i++) + { + run_cmd(invalid_syntax_tests[i], &exit_code); + ok(exit_code == 16, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(invalid_syntax_tests[i])); + } + + winetest_push_context("basic copy test 1"); + create_test_source_folder(); + run_cmd(L"robocopy source destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 2"); + create_test_source_folder(); + run_cmd(L"robocopy ./source third_folder/../destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 3"); + create_test_source_folder(); + create_test_folder(L"destination"); + create_test_file(L"destination\fileA.a", 9000); + run_cmd(L"robocopy ./source source/../destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_2_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 4"); + create_test_source_folder(); + swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), + L"robocopy %s\source %s\destination /r:1 /w:0", + temp_path, temp_path); + run_cmd(temp_cmd, &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 5"); + create_test_source_folder(); + swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), + L"robocopy %s\source destination /r:1 /w:0", + temp_path); + run_cmd(temp_cmd, &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 6"); + create_test_source_folder(); + swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), + L"robocopy source %s\destination /r:1 /w:0", + temp_path); + run_cmd(temp_cmd, &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 7"); + create_test_source_folder(); + swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), + L"robocopy %s\third_folder\..\source %s\third_folder\..\destination /r:1 /w:0", + temp_path, temp_path); + run_cmd(temp_cmd, &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("invalid copy test 1"); + create_test_source_folder(); + run_cmd(L"robocopy source source /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 0, "unexpected exit code %d\n", exit_code); + check_no_copy_test(); + winetest_pop_context(); + + winetest_push_context("invalid copy test 2"); + create_test_source_folder(); + run_cmd(L"robocopy invalid_folder destination /r:1 /w:0", &exit_code); + ok(exit_code == 16, "unexpected exit code %d\n", exit_code); + check_no_copy_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 1"); + create_test_source_folder(); + run_cmd(L"robocopy source destination file?.? /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 2"); + create_test_source_folder(); + run_cmd(L"robocopy source destination file* /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 3"); + create_test_source_folder(); + run_cmd(L"robocopy source destination *le?.? /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 4"); + create_test_source_folder(); + run_cmd(L"robocopy source destination *A.? /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_wildcard_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 5"); + create_test_source_folder(); + run_cmd(L"robocopy sour?e destination /r:1 /w:0", &exit_code); + ok(exit_code == 16, "unexpected exit code %d\n", exit_code); + check_no_copy_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 6"); + create_test_source_folder(); + run_cmd(L"robocopy s* destination /r:1 /w:0", &exit_code); + ok(exit_code == 16, "unexpected exit code %d\n", exit_code); + check_no_copy_test(); + winetest_pop_context(); + + winetest_push_context("flag parser test 1"); + create_test_source_folder(); + run_cmd(L"robocopy /r:1 source destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("flag parser test 2"); + create_test_source_folder(); + run_cmd(L"robocopy source /r:1 destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + SetCurrentDirectoryW(L".."); + ok(RemoveDirectoryW(temp_path), "could not remove temp path %s, error %d\n", debugstr_w(temp_path), GetLastError()); +}