mirror of
https://github.com/reactos/reactos.git
synced 2025-01-03 21:09:19 +00:00
[KMTESTS]
- rework the kmtest loader application to follow everything else's coding style and to allow user-mode test parts svn path=/branches/GSoC_2011/KMTestSuite/; revision=52519
This commit is contained in:
parent
991bc0e366
commit
459f4ac630
8 changed files with 803 additions and 238 deletions
|
@ -48,6 +48,8 @@ set_rc_compiler()
|
|||
list(APPEND KMTEST_SOURCE
|
||||
kmtest/kmtest.c
|
||||
kmtest/service.c
|
||||
kmtest/support.c
|
||||
kmtest/testlist.c
|
||||
kmtest/kmtest.rc)
|
||||
|
||||
add_executable(kmtest ${KMTEST_SOURCE})
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <stdarg.h>
|
||||
|
||||
typedef VOID KMT_TESTFUNC(VOID);
|
||||
typedef KMT_TESTFUNC *PKMT_TESTFUNC;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
@ -37,6 +38,17 @@ typedef struct
|
|||
CHAR LogBuffer[ANYSIZE_ARRAY];
|
||||
} KMT_RESULTBUFFER, *PKMT_RESULTBUFFER;
|
||||
|
||||
#if defined KMT_USER_MODE
|
||||
VOID KmtLoadDriver(IN PCWSTR ServiceName, IN BOOLEAN RestartIfRunning);
|
||||
VOID KmtUnloadDriver(VOID);
|
||||
VOID KmtOpenDriver(VOID);
|
||||
VOID KmtCloseDriver(VOID);
|
||||
|
||||
DWORD KmtSendToDriver(IN DWORD ControlCode);
|
||||
DWORD KmtSendStringToDriver(IN DWORD ControlCode, IN PCSTR String);
|
||||
DWORD KmtSendBufferToDriver(IN DWORD ControlCode, IN OUT PVOID Buffer, IN DWORD Length);
|
||||
#endif /* defined KMT_USER_MODE */
|
||||
|
||||
extern PKMT_RESULTBUFFER ResultBuffer;
|
||||
|
||||
#ifdef __GNUC__
|
||||
|
@ -47,13 +59,15 @@ extern PKMT_RESULTBUFFER ResultBuffer;
|
|||
|
||||
#define START_TEST(name) VOID Test_##name(VOID)
|
||||
|
||||
#ifndef KMT_STRINGIZE
|
||||
#define KMT_STRINGIZE(x) #x
|
||||
#define ok(test, ...) ok_(test, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define trace(...) trace_( __FILE__, __LINE__, __VA_ARGS__)
|
||||
#endif /* !defined KMT_STRINGIZE */
|
||||
#define ok(test, ...) ok_(test, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define trace(...) trace_( __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define skip(test, ...) skip_(test, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
#define ok_(test, file, line, ...) KmtOk(test, file ":" KMT_STRINGIZE(line), __VA_ARGS__)
|
||||
#define trace_(file, line, ...) KmtTrace( file ":" KMT_STRINGIZE(line), __VA_ARGS__)
|
||||
#define ok_(test, file, line, ...) KmtOk(test, file ":" KMT_STRINGIZE(line), __VA_ARGS__)
|
||||
#define trace_(file, line, ...) KmtTrace( file ":" KMT_STRINGIZE(line), __VA_ARGS__)
|
||||
#define skip_(test, file, line, ...) KmtSkip(test, file ":" KMT_STRINGIZE(line), __VA_ARGS__)
|
||||
|
||||
VOID KmtVOk(INT Condition, PCSTR FileAndLine, PCSTR Format, va_list Arguments) KMT_FORMAT(ms_printf, 3, 0);
|
||||
|
@ -75,7 +89,9 @@ BOOLEAN KmtSkip(INT Condition, PCSTR FileAndLine, PCSTR Format, ...)
|
|||
#define ok_eq_hex(value, expected) ok_eq_print(value, expected, "0x%08lx")
|
||||
#define ok_bool_true(value, desc) ok((value) == TRUE, desc " FALSE, expected TRUE\n")
|
||||
#define ok_bool_false(value, desc) ok((value) == FALSE, desc " TRUE, expected FALSE\n")
|
||||
#define ok_eq_bool(value, expected) ok((value) == (expected), #value " = %s, expected %s\n", (value) ? "TRUE" : "FALSE", (expected) ? "TRUE" : "FALSE")
|
||||
#define ok_eq_bool(value, expected) ok((value) == (expected), #value " = %s, expected %s\n", \
|
||||
(value) ? "TRUE" : "FALSE", \
|
||||
(expected) ? "TRUE" : "FALSE")
|
||||
#define ok_eq_str(value, expected) ok(!strcmp(value, expected), #value " = \"%s\", expected \"%s\"\n", value, expected)
|
||||
#define ok_eq_wstr(value, expected) ok(!wcscmp(value, expected), #value " = \"%ls\", expected \"%ls\"\n", value, expected)
|
||||
|
||||
|
|
|
@ -5,5 +5,7 @@
|
|||
<directory name="kmtest">
|
||||
<file>kmtest.c</file>
|
||||
<file>service.c</file>
|
||||
<file>support.c</file>
|
||||
<file>testlist.c</file>
|
||||
</directory>
|
||||
</module>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#define UNICODE
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <strsafe.h>
|
||||
|
||||
|
@ -19,186 +20,288 @@
|
|||
#define KMT_DEFINE_TEST_FUNCTIONS
|
||||
#include <kmt_test.h>
|
||||
|
||||
#define LOGBUFFER_SIZE 65000
|
||||
#define SERVICE_NAME L"Kmtest"
|
||||
#define SERVICE_PATH L"kmtest_drv.sys"
|
||||
|
||||
static void OutputError(FILE *fp, DWORD error);
|
||||
static DWORD RunTest(char *testName);
|
||||
static DWORD ListTests(PSTR *testList);
|
||||
int __cdecl main(int argc, char **argv);
|
||||
#define LOGBUFFER_SIZE 65000
|
||||
#define RESULTBUFFER_SIZE FIELD_OFFSET(KMT_RESULTBUFFER, LogBuffer[LOGBUFFER_SIZE])
|
||||
|
||||
static void OutputError(FILE *fp, DWORD error)
|
||||
static HANDLE KmtestHandle;
|
||||
PCSTR ErrorFileAndLine = "No error";
|
||||
|
||||
static void OutputError(DWORD Error);
|
||||
static DWORD ListTests(VOID);
|
||||
static PKMT_TESTFUNC FindTest(PCSTR TestName);
|
||||
static DWORD OutputResult(PCSTR TestName);
|
||||
static DWORD RunTest(PCSTR TestName);
|
||||
int __cdecl main(int ArgCount, char **Arguments);
|
||||
|
||||
/**
|
||||
* @name OutputError
|
||||
*
|
||||
* Output an error message to the console.
|
||||
*
|
||||
* @param Error
|
||||
* Win32 error code
|
||||
*/
|
||||
static
|
||||
void
|
||||
OutputError(
|
||||
DWORD Error)
|
||||
{
|
||||
char *message;
|
||||
PSTR Message;
|
||||
if (!FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
||||
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message, 0, NULL))
|
||||
NULL, Error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&Message, 0, NULL))
|
||||
{
|
||||
fprintf(fp, "Could not retrieve error message (error 0x%08lx). Original error: 0x%08lx\n", GetLastError(), error);
|
||||
fprintf(stderr, "%s: Could not retrieve error message (error 0x%08lx). Original error: 0x%08lx\n",
|
||||
ErrorFileAndLine, GetLastError(), Error);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "%s\n", message);
|
||||
fprintf(stderr, "%s: error 0x%08lx: %s\n", ErrorFileAndLine, Error, Message);
|
||||
|
||||
LocalFree(message);
|
||||
LocalFree(Message);
|
||||
}
|
||||
|
||||
static DWORD RunTest(char *testName)
|
||||
/**
|
||||
* @name ListTests
|
||||
*
|
||||
* Output the list of tests to the console.
|
||||
* The list will comprise tests as listed by the driver
|
||||
* in addition to user-mode tests in TestList.
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
static
|
||||
DWORD
|
||||
ListTests(VOID)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
HANDLE hDevice = INVALID_HANDLE_VALUE;
|
||||
DWORD bytesRead, bytesWritten;
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
CHAR Buffer[1024];
|
||||
DWORD BytesRead;
|
||||
PCSTR TestName = Buffer;
|
||||
PCKMT_TEST TestEntry = TestList;
|
||||
PCSTR NextTestName;
|
||||
|
||||
puts("Valid test names:");
|
||||
|
||||
// get test list from driver
|
||||
if (!DeviceIoControl(KmtestHandle, IOCTL_KMTEST_GET_TESTS, NULL, 0, Buffer, sizeof Buffer, &BytesRead, NULL))
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
// output test list plus user-mode tests
|
||||
while (TestEntry->TestName || *TestName)
|
||||
{
|
||||
// tests starting with a '-' should not be listed
|
||||
while (TestEntry->TestName && *TestEntry->TestName == '-')
|
||||
++TestEntry;
|
||||
|
||||
if (!TestEntry->TestName)
|
||||
{
|
||||
NextTestName = TestName;
|
||||
TestName += strlen(TestName) + 1;
|
||||
}
|
||||
else if (!*TestName)
|
||||
{
|
||||
NextTestName = TestEntry->TestName;
|
||||
++TestEntry;
|
||||
}
|
||||
else
|
||||
{
|
||||
int Result = strcmp(TestEntry->TestName, TestName);
|
||||
|
||||
if (Result == 0)
|
||||
{
|
||||
NextTestName = TestEntry->TestName;
|
||||
TestName += strlen(TestName) + 1;
|
||||
++TestEntry;
|
||||
}
|
||||
else if (Result < 0)
|
||||
{
|
||||
NextTestName = TestEntry->TestName;
|
||||
++TestEntry;
|
||||
}
|
||||
else
|
||||
{
|
||||
NextTestName = TestName;
|
||||
TestName += strlen(TestName) + 1;
|
||||
}
|
||||
}
|
||||
printf(" %s\n", NextTestName);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
return Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name FindTest
|
||||
*
|
||||
* Find a test in TestList by name.
|
||||
*
|
||||
* @param TestName
|
||||
* Name of the test to look for. Case sensitive
|
||||
*
|
||||
* @return pointer to test function, or NULL if not found
|
||||
*/
|
||||
static
|
||||
PKMT_TESTFUNC
|
||||
FindTest(
|
||||
PCSTR TestName)
|
||||
{
|
||||
PCKMT_TEST TestEntry = TestList;
|
||||
|
||||
for (TestEntry = TestList; TestEntry->TestName; ++TestEntry)
|
||||
{
|
||||
PCSTR TestEntryName = TestEntry->TestName;
|
||||
|
||||
// skip leading '-' if present
|
||||
if (*TestEntryName == '-')
|
||||
++TestEntryName;
|
||||
|
||||
if (!lstrcmpA(TestEntryName, TestName))
|
||||
break;
|
||||
}
|
||||
|
||||
return TestEntry->TestFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name OutputResult
|
||||
*
|
||||
* Output the test results in ResultBuffer to the console.
|
||||
*
|
||||
* @param TestName
|
||||
* Name of the test whose result is to be printed
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
static
|
||||
DWORD
|
||||
OutputResult(
|
||||
PCSTR TestName)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
DWORD BytesWritten;
|
||||
|
||||
KmtFinishTest(TestName);
|
||||
|
||||
if (!WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), ResultBuffer->LogBuffer, ResultBuffer->LogBufferLength, &BytesWritten, NULL))
|
||||
Error = GetLastError();
|
||||
|
||||
return Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name RunTest
|
||||
*
|
||||
* Run the named test and output its results.
|
||||
*
|
||||
* @param TestName
|
||||
* Name of the test to run. Case sensitive
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
static
|
||||
DWORD
|
||||
RunTest(
|
||||
PCSTR TestName)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
PKMT_TESTFUNC TestFunction;
|
||||
DWORD BytesRead;
|
||||
|
||||
ResultBuffer = KmtAllocateResultBuffer(LOGBUFFER_SIZE);
|
||||
if (!ResultBuffer)
|
||||
if (!DeviceIoControl(KmtestHandle, IOCTL_KMTEST_SET_RESULTBUFFER, ResultBuffer, RESULTBUFFER_SIZE, NULL, 0, &BytesRead, NULL))
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
// check test list
|
||||
TestFunction = FindTest(TestName);
|
||||
|
||||
if (TestFunction)
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
hDevice = CreateFile(KMTEST_DEVICE_PATH, GENERIC_READ | GENERIC_WRITE, 0,
|
||||
NULL, OPEN_EXISTING, 0, NULL);
|
||||
|
||||
if (hDevice == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!DeviceIoControl(hDevice, IOCTL_KMTEST_SET_RESULTBUFFER, ResultBuffer, FIELD_OFFSET(KMT_RESULTBUFFER, LogBuffer[LOGBUFFER_SIZE]), NULL, 0, &bytesRead, NULL))
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!DeviceIoControl(hDevice, IOCTL_KMTEST_RUN_TEST, testName, strlen(testName), NULL, 0, &bytesRead, NULL))
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
KmtFinishTest(testName);
|
||||
|
||||
if (!WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), ResultBuffer->LogBuffer, ResultBuffer->LogBufferLength, &bytesWritten, NULL))
|
||||
{
|
||||
error = GetLastError();
|
||||
TestFunction();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// not found in user-mode test list, call driver
|
||||
if (!DeviceIoControl(KmtestHandle, IOCTL_KMTEST_RUN_TEST, (PVOID)TestName, strlen(TestName), NULL, 0, &BytesRead, NULL))
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
cleanup:
|
||||
if (hDevice != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hDevice);
|
||||
OutputResult(TestName);
|
||||
|
||||
KmtFreeResultBuffer(ResultBuffer);
|
||||
|
||||
if (ResultBuffer)
|
||||
KmtFreeResultBuffer(ResultBuffer);
|
||||
|
||||
return error;
|
||||
return Error;
|
||||
}
|
||||
|
||||
static DWORD ListTests(PSTR *testList)
|
||||
/**
|
||||
* @name main
|
||||
*
|
||||
* Program entry point
|
||||
*
|
||||
* @param ArgCount
|
||||
* @param Arguments
|
||||
*
|
||||
* @return EXIT_SUCCESS on success, EXIT_FAILURE on failure
|
||||
*/
|
||||
int
|
||||
main(
|
||||
int ArgCount,
|
||||
char **Arguments)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
HANDLE hDevice = INVALID_HANDLE_VALUE;
|
||||
DWORD bytesRead;
|
||||
PSTR buffer = NULL;
|
||||
DWORD bufferSize;
|
||||
INT Status = EXIT_SUCCESS;
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
SC_HANDLE ServiceHandle;
|
||||
PCSTR AppName = "kmtest.exe";
|
||||
PCSTR TestName;
|
||||
|
||||
if (!testList)
|
||||
{
|
||||
error = ERROR_INVALID_PARAMETER;
|
||||
Error = KmtServiceInit();
|
||||
if (Error)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
hDevice = CreateFile(KMTEST_DEVICE_PATH, GENERIC_READ | GENERIC_WRITE, 0,
|
||||
NULL, OPEN_EXISTING, 0, NULL);
|
||||
|
||||
if (hDevice == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
error = GetLastError();
|
||||
Error = KmtCreateAndStartService(SERVICE_NAME, SERVICE_PATH, L"ReactOS Kernel-Mode Test Suite Driver", &ServiceHandle, FALSE);
|
||||
if (Error)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
bufferSize = 1024;
|
||||
buffer = HeapAlloc(GetProcessHeap(), 0, bufferSize);
|
||||
if (!buffer)
|
||||
KmtestHandle = CreateFile(KMTEST_DEVICE_PATH, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (KmtestHandle == INVALID_HANDLE_VALUE)
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
if (ArgCount >= 1)
|
||||
AppName = Arguments[0];
|
||||
|
||||
if (ArgCount <= 1)
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!DeviceIoControl(hDevice, IOCTL_KMTEST_GET_TESTS, NULL, 0, buffer, bufferSize, &bytesRead, NULL))
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (buffer && error)
|
||||
{
|
||||
HeapFree(GetProcessHeap(), 0, buffer);
|
||||
buffer = NULL;
|
||||
}
|
||||
|
||||
if (hDevice != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hDevice);
|
||||
|
||||
if (testList)
|
||||
*testList = buffer;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int __cdecl main(int argc, char **argv)
|
||||
{
|
||||
int status = EXIT_SUCCESS;
|
||||
DWORD error;
|
||||
|
||||
if (argc <= 1)
|
||||
{
|
||||
/* no arguments: show usage and list tests */
|
||||
char *programName = argc == 0 ? "kmtest" : argv[0];
|
||||
char *testNames, *testName;
|
||||
size_t len;
|
||||
|
||||
printf("Usage: %s test_name\n", programName);
|
||||
printf(" %s <Create|Start|Stop|Delete>\n", programName);
|
||||
puts("\nValid test names:");
|
||||
|
||||
error = ListTests(&testNames);
|
||||
testName = testNames;
|
||||
|
||||
while ((len = strlen(testName)) != 0)
|
||||
{
|
||||
printf(" %s\n", testName);
|
||||
testName += len + 1;
|
||||
}
|
||||
|
||||
/* TODO: user-mode test parts */
|
||||
|
||||
if (error)
|
||||
OutputError(stdout, error);
|
||||
printf("Usage: %s <test_name> - run the specified test\n", AppName);
|
||||
printf(" %s --list - list available tests\n", AppName);
|
||||
printf(" %s <create|delete|start|stop> - manage the kmtest driver\n\n", AppName);
|
||||
Error = ListTests();
|
||||
}
|
||||
else
|
||||
{
|
||||
char *testName = argv[1];
|
||||
|
||||
if (argc > 2)
|
||||
fputs("Excess arguments ignored\n", stderr);
|
||||
|
||||
if (!lstrcmpiA(testName, "create"))
|
||||
error = Service_Control(Service_Create);
|
||||
else if (!lstrcmpiA(testName, "delete"))
|
||||
error = Service_Control(Service_Delete);
|
||||
else if (!lstrcmpiA(testName, "start"))
|
||||
error = Service_Control(Service_Start);
|
||||
else if (!lstrcmpiA(testName, "stop"))
|
||||
error = Service_Control(Service_Stop);
|
||||
TestName = Arguments[1];
|
||||
if (!lstrcmpA(Arguments[1], "--list"))
|
||||
Error = ListTests();
|
||||
else
|
||||
/* TODO: user-mode test parts */
|
||||
error = RunTest(testName);
|
||||
|
||||
OutputError(stdout, error);
|
||||
Error = RunTest(TestName);
|
||||
}
|
||||
|
||||
if (error)
|
||||
status = EXIT_FAILURE;
|
||||
cleanup:
|
||||
if (KmtestHandle)
|
||||
CloseHandle(KmtestHandle);
|
||||
|
||||
return status;
|
||||
if (Error)
|
||||
KmtServiceCleanup(TRUE);
|
||||
else
|
||||
Error = KmtServiceCleanup(FALSE);
|
||||
|
||||
if (Error)
|
||||
OutputError(Error);
|
||||
|
||||
if (Error)
|
||||
Status = EXIT_FAILURE;
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
|
|
@ -8,16 +8,57 @@
|
|||
#ifndef _KMTESTS_H_
|
||||
#define _KMTESTS_H_
|
||||
|
||||
#include <windows.h>
|
||||
extern PCSTR ErrorFileAndLine;
|
||||
|
||||
/* service control functions */
|
||||
typedef DWORD SERVICE_FUNC(SC_HANDLE hManager);
|
||||
#ifndef KMT_STRINGIZE
|
||||
#define KMT_STRINGIZE(x) #x
|
||||
#endif /* !defined KMT_STRINGIZE */
|
||||
|
||||
SERVICE_FUNC Service_Create;
|
||||
SERVICE_FUNC Service_Delete;
|
||||
SERVICE_FUNC Service_Start;
|
||||
SERVICE_FUNC Service_Stop;
|
||||
#define location(file, line) do { ErrorFileAndLine = file ":" KMT_STRINGIZE(line); } while (0)
|
||||
#define error_value(Error, value) do { location(__FILE__, __LINE__); Error = value; } while (0)
|
||||
#define error(Error) error_value(Error, GetLastError())
|
||||
#define error_goto(Error, label) do { error(Error); goto label; } while (0)
|
||||
#define error_value_goto(Error, value, label) do { error_value(Error, value); goto label; } while (0)
|
||||
|
||||
DWORD Service_Control(SERVICE_FUNC *Service_Func);
|
||||
/* service management functions */
|
||||
DWORD
|
||||
KmtServiceInit(VOID);
|
||||
|
||||
DWORD
|
||||
KmtServiceCleanup(
|
||||
BOOLEAN IgnoreErrors);
|
||||
|
||||
DWORD
|
||||
KmtCreateService(
|
||||
IN PCWSTR ServiceName,
|
||||
IN PCWSTR ServicePath,
|
||||
IN PCWSTR DisplayName OPTIONAL,
|
||||
OUT SC_HANDLE *ServiceHandle);
|
||||
|
||||
DWORD
|
||||
KmtStartService(
|
||||
IN PCWSTR ServiceName OPTIONAL,
|
||||
IN OUT SC_HANDLE *ServiceHandle);
|
||||
|
||||
DWORD
|
||||
KmtCreateAndStartService(
|
||||
IN PCWSTR ServiceName,
|
||||
IN PCWSTR ServicePath,
|
||||
IN PCWSTR DisplayName OPTIONAL,
|
||||
OUT SC_HANDLE *ServiceHandle,
|
||||
IN BOOLEAN RestartIfRunning);
|
||||
|
||||
DWORD
|
||||
KmtStopService(
|
||||
IN PCWSTR ServiceName OPTIONAL,
|
||||
IN OUT SC_HANDLE *ServiceHandle);
|
||||
|
||||
DWORD
|
||||
KmtDeleteService(
|
||||
IN PCWSTR ServiceName OPTIONAL,
|
||||
IN OUT SC_HANDLE *ServiceHandle);
|
||||
|
||||
DWORD KmtCloseService(
|
||||
IN OUT SC_HANDLE *ServiceHandle);
|
||||
|
||||
#endif /* !defined _KMTESTS_H_ */
|
||||
|
|
|
@ -6,130 +6,308 @@
|
|||
*/
|
||||
|
||||
#define UNICODE
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <strsafe.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "kmtest.h"
|
||||
|
||||
#define SERVICE_NAME L"Kmtest"
|
||||
#define SERVICE_PATH L"\\kmtest_drv.sys"
|
||||
#define SERVICE_ACCESS (SERVICE_START | SERVICE_STOP | DELETE)
|
||||
|
||||
DWORD Service_Create(SC_HANDLE hScm)
|
||||
static SC_HANDLE ScmHandle;
|
||||
|
||||
/**
|
||||
* @name KmtServiceInit
|
||||
*
|
||||
* Initialize service management routines (by opening the service control manager)
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
DWORD
|
||||
KmtServiceInit(VOID)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
SC_HANDLE hService = NULL;
|
||||
wchar_t driverPath[MAX_PATH];
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
|
||||
assert(!ScmHandle);
|
||||
|
||||
ScmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
||||
if (!ScmHandle)
|
||||
error(Error);
|
||||
|
||||
return Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KmtServiceCleanup
|
||||
*
|
||||
* Clean up resources used by service management routines.
|
||||
*
|
||||
* @param IgnoreErrors
|
||||
* If TRUE, the function will never set ErrorLineAndFile, and always return ERROR_SUCCESS
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
DWORD
|
||||
KmtServiceCleanup(
|
||||
BOOLEAN IgnoreErrors)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
|
||||
if (ScmHandle && !CloseServiceHandle(ScmHandle) && !IgnoreErrors)
|
||||
error(Error);
|
||||
|
||||
return Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KmtCreateService
|
||||
*
|
||||
* Create the specified driver service and return a handle to it
|
||||
*
|
||||
* @param ServiceName
|
||||
* Name of the service to create
|
||||
* @param ServicePath
|
||||
* File name of the driver, relative to the current directory
|
||||
* @param DisplayName
|
||||
* Service display name
|
||||
* @param ServiceHandle
|
||||
* Pointer to a variable to receive the handle to the service
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
DWORD
|
||||
KmtCreateService(
|
||||
IN PCWSTR ServiceName,
|
||||
IN PCWSTR ServicePath,
|
||||
IN PCWSTR DisplayName OPTIONAL,
|
||||
OUT SC_HANDLE *ServiceHandle)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
WCHAR DriverPath[MAX_PATH];
|
||||
HRESULT result = S_OK;
|
||||
|
||||
if (!GetCurrentDirectory(sizeof driverPath / sizeof driverPath[0], driverPath)
|
||||
|| FAILED(result = StringCbCat(driverPath, sizeof driverPath, SERVICE_PATH)))
|
||||
assert(ServiceHandle);
|
||||
assert(ServiceName && ServicePath);
|
||||
|
||||
if (!GetCurrentDirectory(sizeof DriverPath / sizeof DriverPath[0], DriverPath))
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
if (DriverPath[wcslen(DriverPath) - 1] != L'\\')
|
||||
{
|
||||
if (FAILED(result))
|
||||
error = result;
|
||||
else
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
DriverPath[wcslen(DriverPath) + 1] = L'\0';
|
||||
DriverPath[wcslen(DriverPath)] = L'\\';
|
||||
}
|
||||
|
||||
hService = CreateService(hScm, SERVICE_NAME, L"ReactOS Kernel-Mode Test Suite Driver",
|
||||
SERVICE_START, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START,
|
||||
SERVICE_ERROR_NORMAL, driverPath, NULL, NULL, NULL, NULL, NULL);
|
||||
result = StringCbCat(DriverPath, sizeof DriverPath, ServicePath);
|
||||
if (FAILED(result))
|
||||
error_value_goto(Error, result, cleanup);
|
||||
|
||||
if (!hService)
|
||||
error = GetLastError();
|
||||
*ServiceHandle = CreateService(ScmHandle, ServiceName, DisplayName,
|
||||
SERVICE_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START,
|
||||
SERVICE_ERROR_NORMAL, DriverPath, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
if (!*ServiceHandle)
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
cleanup:
|
||||
return error;
|
||||
return Error;
|
||||
}
|
||||
|
||||
DWORD Service_Delete(SC_HANDLE hScm)
|
||||
/**
|
||||
* @name KmtStartService
|
||||
*
|
||||
* Start the specified driver service by handle or name (and return a handle to it)
|
||||
*
|
||||
* @param ServiceName
|
||||
* If *ServiceHandle is NULL, name of the service to start
|
||||
* @param ServiceHandle
|
||||
* Pointer to a variable containing the service handle,
|
||||
* or NULL (in which case it will be filled with a handle to the service)
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
DWORD
|
||||
KmtStartService(
|
||||
IN PCWSTR ServiceName OPTIONAL,
|
||||
IN OUT SC_HANDLE *ServiceHandle)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
SC_HANDLE hService = NULL;
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
|
||||
hService = OpenService(hScm, SERVICE_NAME, DELETE);
|
||||
assert(ServiceHandle);
|
||||
assert(ServiceName || *ServiceHandle);
|
||||
|
||||
if (!hService)
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
if (!*ServiceHandle)
|
||||
*ServiceHandle = OpenService(ScmHandle, ServiceName, SERVICE_ACCESS);
|
||||
|
||||
if (!DeleteService(hService))
|
||||
error = GetLastError();
|
||||
if (!*ServiceHandle)
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
if (!StartService(*ServiceHandle, 0, NULL))
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
cleanup:
|
||||
if (hService)
|
||||
CloseServiceHandle(hService);
|
||||
|
||||
return error;
|
||||
return Error;
|
||||
}
|
||||
|
||||
DWORD Service_Start(SC_HANDLE hScm)
|
||||
/**
|
||||
* @name KmtCreateAndStartService
|
||||
*
|
||||
* Create and start the specified driver service and return a handle to it
|
||||
*
|
||||
* @param ServiceName
|
||||
* Name of the service to create
|
||||
* @param ServicePath
|
||||
* File name of the driver, relative to the current directory
|
||||
* @param DisplayName
|
||||
* Service display name
|
||||
* @param ServiceHandle
|
||||
* Pointer to a variable to receive the handle to the service
|
||||
* @param RestartIfRunning
|
||||
* TRUE to stop and restart the service if it is already running
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
DWORD
|
||||
KmtCreateAndStartService(
|
||||
IN PCWSTR ServiceName,
|
||||
IN PCWSTR ServicePath,
|
||||
IN PCWSTR DisplayName OPTIONAL,
|
||||
OUT SC_HANDLE *ServiceHandle,
|
||||
IN BOOLEAN RestartIfRunning)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
SC_HANDLE hService = NULL;
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
|
||||
hService = OpenService(hScm, SERVICE_NAME, SERVICE_START);
|
||||
assert(ServiceHandle);
|
||||
|
||||
if (!hService)
|
||||
{
|
||||
error = GetLastError();
|
||||
Error = KmtCreateService(ServiceName, ServicePath, DisplayName, ServiceHandle);
|
||||
|
||||
if (Error && Error != ERROR_SERVICE_EXISTS)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!StartService(hService, 0, NULL))
|
||||
error = GetLastError();
|
||||
Error = KmtStartService(ServiceName, ServiceHandle);
|
||||
|
||||
if (Error != ERROR_SERVICE_ALREADY_RUNNING)
|
||||
goto cleanup;
|
||||
|
||||
Error = ERROR_SUCCESS;
|
||||
|
||||
if (!RestartIfRunning)
|
||||
goto cleanup;
|
||||
|
||||
Error = KmtStopService(ServiceName, ServiceHandle);
|
||||
if (Error)
|
||||
goto cleanup;
|
||||
|
||||
Error = KmtStartService(ServiceName, ServiceHandle);
|
||||
if (Error)
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
if (hService)
|
||||
CloseServiceHandle(hService);
|
||||
|
||||
return error;
|
||||
assert(Error || *ServiceHandle);
|
||||
return Error;
|
||||
}
|
||||
|
||||
DWORD Service_Stop(SC_HANDLE hScm)
|
||||
/**
|
||||
* @name KmtStopService
|
||||
*
|
||||
* Stop the specified driver service by handle or name (and return a handle to it)
|
||||
*
|
||||
* @param ServiceName
|
||||
* If *ServiceHandle is NULL, name of the service to stop
|
||||
* @param ServiceHandle
|
||||
* Pointer to a variable containing the service handle,
|
||||
* or NULL (in which case it will be filled with a handle to the service)
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
DWORD
|
||||
KmtStopService(
|
||||
IN PCWSTR ServiceName OPTIONAL,
|
||||
IN OUT SC_HANDLE *ServiceHandle)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
SC_HANDLE hService = NULL;
|
||||
SERVICE_STATUS serviceStatus;
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
SERVICE_STATUS ServiceStatus;
|
||||
|
||||
hService = OpenService(hScm, SERVICE_NAME, SERVICE_STOP);
|
||||
assert(ServiceHandle);
|
||||
assert(ServiceName || *ServiceHandle);
|
||||
|
||||
if (!hService)
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
if (!*ServiceHandle)
|
||||
*ServiceHandle = OpenService(ScmHandle, ServiceName, SERVICE_ACCESS);
|
||||
|
||||
if (!ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus))
|
||||
error = GetLastError();
|
||||
if (!*ServiceHandle)
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
if (!ControlService(*ServiceHandle, SERVICE_CONTROL_STOP, &ServiceStatus))
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
cleanup:
|
||||
if (hService)
|
||||
CloseServiceHandle(hService);
|
||||
|
||||
return error;
|
||||
return Error;
|
||||
}
|
||||
|
||||
DWORD Service_Control(SERVICE_FUNC *Service_Func)
|
||||
/**
|
||||
* @name KmtDeleteService
|
||||
*
|
||||
* Delete the specified driver service by handle or name (and return a handle to it)
|
||||
*
|
||||
* @param ServiceName
|
||||
* If *ServiceHandle is NULL, name of the service to delete
|
||||
* @param ServiceHandle
|
||||
* Pointer to a variable containing the service handle.
|
||||
* Will be set to NULL on success
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
DWORD
|
||||
KmtDeleteService(
|
||||
IN PCWSTR ServiceName OPTIONAL,
|
||||
IN OUT SC_HANDLE *ServiceHandle)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
SC_HANDLE hScm = NULL;
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
|
||||
hScm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
||||
assert(ServiceHandle);
|
||||
assert(ServiceName || *ServiceHandle);
|
||||
|
||||
if (!hScm)
|
||||
{
|
||||
error = GetLastError();
|
||||
goto cleanup;
|
||||
}
|
||||
if (!*ServiceHandle)
|
||||
*ServiceHandle = OpenService(ScmHandle, ServiceName, SERVICE_ACCESS);
|
||||
|
||||
error = Service_Func(hScm);
|
||||
if (!*ServiceHandle)
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
if (!DeleteService(*ServiceHandle))
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
if (*ServiceHandle)
|
||||
CloseServiceHandle(*ServiceHandle);
|
||||
|
||||
cleanup:
|
||||
if (hScm)
|
||||
CloseServiceHandle(hScm);
|
||||
|
||||
return error;
|
||||
return Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KmtCloseService
|
||||
*
|
||||
* Close the specified driver service handle
|
||||
*
|
||||
* @param ServiceHandle
|
||||
* Pointer to a variable containing the service handle.
|
||||
* Will be set to NULL on success
|
||||
*
|
||||
* @return Win32 error code
|
||||
*/
|
||||
DWORD KmtCloseService(
|
||||
IN OUT SC_HANDLE *ServiceHandle)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
|
||||
assert(ServiceHandle);
|
||||
|
||||
if (*ServiceHandle && !CloseServiceHandle(*ServiceHandle))
|
||||
error_goto(Error, cleanup);
|
||||
|
||||
*ServiceHandle = NULL;
|
||||
|
||||
cleanup:
|
||||
return Error;
|
||||
}
|
||||
|
|
197
kmtests/kmtest/support.c
Normal file
197
kmtests/kmtest/support.c
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* PROJECT: ReactOS kernel-mode tests
|
||||
* LICENSE: GPLv2+ - See COPYING in the top level directory
|
||||
* PURPOSE: Kernel-Mode Test Suite Driver
|
||||
* PROGRAMMER: Thomas Faber <thfabba@gmx.de>
|
||||
*/
|
||||
|
||||
#define UNICODE
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <strsafe.h>
|
||||
|
||||
#include "kmtest.h"
|
||||
#include <kmt_test.h>
|
||||
#include <kmt_public.h>
|
||||
|
||||
/* pseudo-tests */
|
||||
START_TEST(Create)
|
||||
{
|
||||
// nothing to do here. All tests start the service if needed
|
||||
}
|
||||
|
||||
START_TEST(Delete)
|
||||
{
|
||||
// TODO: delete kmtest service
|
||||
}
|
||||
|
||||
START_TEST(Start)
|
||||
{
|
||||
// nothing to do here. All tests start the service
|
||||
}
|
||||
|
||||
START_TEST(Stop)
|
||||
{
|
||||
// TODO: stop kmtest service
|
||||
}
|
||||
|
||||
/* test support functions for special-purpose drivers */
|
||||
|
||||
static WCHAR TestServiceName[MAX_PATH];
|
||||
static SC_HANDLE TestServiceHandle;
|
||||
static HANDLE TestDeviceHandle;
|
||||
|
||||
/**
|
||||
* @name KmtLoadDriver
|
||||
*
|
||||
* Load the specified special-purpose driver (create/start the service)
|
||||
*
|
||||
* @param ServiceName
|
||||
* Name of the driver service (Kmtest- prefix will be added automatically)
|
||||
* @param RestartIfRunning
|
||||
* TRUE to stop and restart the service if it is already running
|
||||
*/
|
||||
VOID
|
||||
KmtLoadDriver(
|
||||
IN PCWSTR ServiceName,
|
||||
IN BOOLEAN RestartIfRunning)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
WCHAR ServicePath[MAX_PATH];
|
||||
|
||||
StringCbCopy(ServicePath, sizeof ServicePath, ServiceName);
|
||||
StringCbCat(ServicePath, sizeof ServicePath, L"_drv.sys");
|
||||
|
||||
StringCbCopy(TestServiceName, sizeof TestServiceName, L"Kmtest-");
|
||||
StringCbCat(TestServiceName, sizeof TestServiceName, ServiceName);
|
||||
|
||||
Error = KmtCreateAndStartService(TestServiceName, ServicePath, NULL, &TestServiceHandle, RestartIfRunning);
|
||||
|
||||
if (Error)
|
||||
{
|
||||
// TODO
|
||||
__debugbreak();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KmtUnloadDriver
|
||||
*
|
||||
* Unload special-purpose driver (stop the service)
|
||||
*/
|
||||
VOID
|
||||
KmtUnloadDriver(VOID)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
|
||||
Error = KmtStopService(TestServiceName, &TestServiceHandle);
|
||||
|
||||
if (Error)
|
||||
{
|
||||
// TODO
|
||||
__debugbreak();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KmtOpenDriver
|
||||
*
|
||||
* Open special-purpose driver (acquire a device handle)
|
||||
*/
|
||||
VOID
|
||||
KmtOpenDriver(VOID)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
WCHAR DevicePath[MAX_PATH];
|
||||
|
||||
StringCbCopy(DevicePath, sizeof DevicePath, L"\\\\.\\Global\\GLOBALROOT\\Device\\");
|
||||
StringCbCat(DevicePath, sizeof DevicePath, TestServiceName);
|
||||
|
||||
TestDeviceHandle = CreateFile(KMTEST_DEVICE_PATH, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (TestDeviceHandle == INVALID_HANDLE_VALUE)
|
||||
error(Error);
|
||||
|
||||
if (Error)
|
||||
{
|
||||
// TODO
|
||||
__debugbreak();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KmtCloseDriver
|
||||
*
|
||||
* Close special-purpose driver (close device handle)
|
||||
*/
|
||||
VOID
|
||||
KmtCloseDriver(VOID)
|
||||
{
|
||||
DWORD Error = ERROR_SUCCESS;
|
||||
|
||||
if (TestDeviceHandle && !CloseHandle(TestDeviceHandle))
|
||||
error(Error);
|
||||
|
||||
if (Error)
|
||||
{
|
||||
// TODO
|
||||
__debugbreak();
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: check if these will be useful */
|
||||
|
||||
/**
|
||||
* @name KmtSendToDriver
|
||||
*
|
||||
* Unload special-purpose driver (stop the service)
|
||||
*
|
||||
* @param ControlCode
|
||||
*
|
||||
* @return Win32 error code as returned by DeviceIoControl
|
||||
*/
|
||||
DWORD
|
||||
KmtSendToDriver(
|
||||
IN DWORD ControlCode)
|
||||
{
|
||||
// TODO
|
||||
return ERROR_CALL_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KmtSendStringToDriver
|
||||
*
|
||||
* Unload special-purpose driver (stop the service)
|
||||
*
|
||||
* @param ControlCode
|
||||
* @param String
|
||||
*
|
||||
* @return Win32 error code as returned by DeviceIoControl
|
||||
*/
|
||||
DWORD
|
||||
KmtSendStringToDriver(
|
||||
IN DWORD ControlCode,
|
||||
IN PCSTR String)
|
||||
{
|
||||
// TODO
|
||||
return ERROR_CALL_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KmtSendBufferToDriver
|
||||
*
|
||||
* @param ControlCode
|
||||
* @param Buffer
|
||||
* @param Length
|
||||
*
|
||||
* @return Win32 error code as returned by DeviceIoControl
|
||||
*/
|
||||
DWORD
|
||||
KmtSendBufferToDriver(
|
||||
IN DWORD ControlCode,
|
||||
IN OUT PVOID Buffer,
|
||||
IN DWORD Length)
|
||||
{
|
||||
// TODO
|
||||
return ERROR_CALL_NOT_IMPLEMENTED;
|
||||
}
|
26
kmtests/kmtest/testlist.c
Normal file
26
kmtests/kmtest/testlist.c
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* PROJECT: ReactOS kernel-mode tests
|
||||
* LICENSE: GPLv2+ - See COPYING in the top level directory
|
||||
* PURPOSE: Kernel-Mode Test Suite user-mode test list
|
||||
* PROGRAMMER: Thomas Faber <thfabba@gmx.de>
|
||||
*/
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define UNICODE
|
||||
#include <windows.h>
|
||||
#include <kmt_test.h>
|
||||
|
||||
VOID Test_Create(VOID);
|
||||
VOID Test_Delete(VOID);
|
||||
VOID Test_Start(VOID);
|
||||
VOID Test_Stop(VOID);
|
||||
|
||||
/* tests with a leading '-' will not be listed */
|
||||
const KMT_TEST TestList[] =
|
||||
{
|
||||
{ "-create", Test_Create },
|
||||
{ "-delete", Test_Delete },
|
||||
{ "-start", Test_Start },
|
||||
{ "-stop", Test_Stop, },
|
||||
{ NULL, NULL },
|
||||
};
|
Loading…
Reference in a new issue