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(a)gmail.com>
---
v8: Added helper functions to check the source folder to reduce amount of
duplicated code, use only the relative path for DeleteFileW / RemoveDirectoryW
and used string arrays to reduce the amount of code necessary for the tests.
The remaining tests that are not using an array / loop structure either break not completly identical
with the other tests or will not be fixed with the same patch (which would in turn break the tests when
implementing the feature, as the test would succeed for most cmd strings, but not for all).
For the cmd return value, this could be fixed with a todo_wine_if and hard coding the array entries that fail,
but then the check_*_test functions would still fail as there are also differences in the resulting files.
Another solution to this problem would be to also give the id of the specific array entry that is checked
as an argument to the check_*_test functions, so that todo_wine_if could be used, but this would
a) require hardcoding array entry ids, which could easily change if tests are added from the top
instead of the end of the array,
b) make the tests more complex, as an additional function is linked to the content of the cmd string array,
c) deviate even further from other tests that are (AFAIK) seen as the "gold standard".
However, if you prefer this solution, feel free to tell me. :-)
Also, basic_copy_tests and absolute_copy_tests could be theoretically merged, as it should not hurt to
use the strings in basic_copy_tests as format strings for swprintf (they don't contain any format specifiers,
causing swprintf to do nothing).
I think it's reasonable to seperate those tests anyway, as it's not directly visible what strings are added to the
strings otherwise (it's IMO much more clear when talking about a test for absolute paths, which requires information only
available at runtime), but if you think otherwise, I'll change it immediately. :-)
---
configure | 1 +
configure.ac | 1 +
programs/robocopy/tests/Makefile.in | 4 +
programs/robocopy/tests/robocopy.c | 342 ++++++++++++++++++++++++++++
4 files changed, 348 insertions(+)
create mode 100644 programs/robocopy/tests/Makefile.in
create mode 100644 programs/robocopy/tests/robocopy.c
diff --git a/configure b/configure
index 0156a507439..542d0fb7233 100755
--- a/configure
+++ b/configure
@@ -19782,6 +19782,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 ab55c72a238..c5913721a3a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3679,6 +3679,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..48075709cf5
--- /dev/null
+++ b/programs/robocopy/tests/robocopy.c
@@ -0,0 +1,342 @@
+/*
+ * 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)
+{
+ ok (DeleteFileW(relative_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)
+{
+ ok (RemoveDirectoryW(relative_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_source_folder_unchanged(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);
+}
+
+static void check_basic_copy_1_test(void)
+{
+ check_source_folder_unchanged();
+ 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_source_folder_unchanged();
+ 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_source_folder_unchanged();
+ 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_source_folder_unchanged();
+ 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;
+ static 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 /?",
+ };
+ static const WCHAR *basic_copy_tests[] =
+ {
+ L"robocopy source destination /r:1 /w:0",
+ L"robocopy ./source destination /r:1 /w:0",
+ L"robocopy source ./destination /r:1 /w:0",
+ L"robocopy ./source ./destination /r:1 /w:0",
+ L"robocopy source third_folder/../destination /r:1 /w:0",
+ L"robocopy ./source third_folder/../destination /r:1 /w:0",
+ L"robocopy ./third_folder/../source third_folder/../destination /r:1 /w:0",
+ L"robocopy ./third_folder/../source ./destination /r:1 /w:0",
+ L"robocopy source destination *le?.? /r:1 /w:0",
+ L"robocopy source destination file?.? /r:1 /w:0",
+ L"robocopy source destination ?ile?.* /r:1 /w:0",
+ L"robocopy source destination f??e* /r:1 /w:0",
+ L"robocopy source destination file* /r:1 /w:0",
+ };
+ static const WCHAR *absolute_copy_tests[] =
+ {
+ L"robocopy %s\\source %s\\destination /r:1 /w:0",
+ L"robocopy %s\\source destination /r:1 /w:0",
+ L"robocopy source %s\\destination /r:1 /w:0",
+ L"robocopy %s\\third_folder\\..\\source %s\\third_folder\\..\\destination /r:1 /w:0",
+ };
+ static const WCHAR *parser_tests[] =
+ {
+ L"robocopy source /r:1 destination /r:1 /w:0",
+ L"robocopy /r:1 source destination /r:1 /w:0",
+ L"robocopy /r:1 /r:1 source destination /r:1 /w:0",
+ L"robocopy /r:1 source /r:1 destination /r:1 /w:0",
+ };
+
+ /* 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]));
+ }
+
+ for (i = 0; i < ARRAY_SIZE(basic_copy_tests); i++)
+ {
+ winetest_push_context("basic copy test %d", i + 1);
+ create_test_source_folder();
+ run_cmd(basic_copy_tests[i], &exit_code);
+ todo_wine ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(basic_copy_tests[i]));
+ check_basic_copy_1_test();
+ winetest_pop_context();
+ }
+
+ winetest_push_context("basic copy test %d", ARRAY_SIZE(basic_copy_tests) + 1);
+ 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();
+
+ for (i = 0; i < ARRAY_SIZE(absolute_copy_tests); i++)
+ {
+ winetest_push_context("absolute copy test %d", i + 1);
+ create_test_source_folder();
+ swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), absolute_copy_tests[i], temp_path, temp_path);
+ run_cmd(temp_cmd, &exit_code);
+ todo_wine ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(absolute_copy_tests[i]));
+ check_basic_copy_1_test();
+ winetest_pop_context();
+ }
+
+ for (i = 0; i < ARRAY_SIZE(parser_tests); i++)
+ {
+ winetest_push_context("parser test %d", i + 1);
+ create_test_source_folder();
+ run_cmd(parser_tests[i], &exit_code);
+ todo_wine ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(parser_tests[i]));
+ 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 *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 2");
+ 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 3");
+ 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();
+
+ SetCurrentDirectoryW(L"..");
+ ok(RemoveDirectoryW(temp_path), "could not remove temp path %s, error %d\n", debugstr_w(temp_path), GetLastError());
+}
--
2.32.0