First shot at test harness
This commit is contained in:
parent
618421ce95
commit
ad94c9297e
95
libol-tests.vcxproj
Normal file
95
libol-tests.vcxproj
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="libol.vcxproj">
|
||||
<Project>{301fe650-cd34-14e5-6b63-42e383fa02bc}</Project>
|
||||
<Private>false</Private>
|
||||
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
<LinkLibraryDependencies>true</LinkLibraryDependencies>
|
||||
<UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="test\echo-server.c" />
|
||||
<ClCompile Include="test\test-fail-always.c" />
|
||||
<ClCompile Include="test\test-pass-always.c" />
|
||||
<ClCompile Include="test\test-ping-pong.c" />
|
||||
<ClCompile Include="test\test-runner-win32.c" />
|
||||
<ClCompile Include="test\test-runner.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="test\test-list.h" />
|
||||
<ClInclude Include="test\test-runner-win32.h" />
|
||||
<ClInclude Include="test\test-runner.h" />
|
||||
<ClInclude Include="test\test.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<TargetName>test</TargetName>
|
||||
<ProjectGUID>{B30D70EF-2678-4393-B322-74E1476757DC}</ProjectGUID>
|
||||
<Keyword>ManagedCProj</Keyword>
|
||||
<RootNamespace>liboltests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(LocalAppData)\Microsoft\VisualStudio\10.0\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(LocalAppData)\Microsoft\VisualStudio\10.0\Microsoft.Cpp.$(Platform).user.props')" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(LocalAppData)\Microsoft\VisualStudio\10.0\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(LocalAppData)\Microsoft\VisualStudio\10.0\Microsoft.Cpp.$(Platform).user.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup>
|
||||
<PostBuildEventCommand>if exist app.config copy app.config "$(OutDir)app.config"</PostBuildEventCommand>
|
||||
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
|
||||
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
|
||||
<TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.exe</TargetExt>
|
||||
<TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.exe</TargetExt>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>ws2_32.lib</AdditionalDependencies>
|
||||
<CLRImageType>Default</CLRImageType>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>ws2_32.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libol", "libol.vcxproj", "{301FE650-CD34-14E5-6B63-42E383FA02BC}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libol-tests", "libol-tests.vcxproj", "{B30D70EF-2678-4393-B322-74E1476757DC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
@ -13,6 +15,10 @@ Global
|
||||
{301FE650-CD34-14E5-6B63-42E383FA02BC}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{301FE650-CD34-14E5-6B63-42E383FA02BC}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{301FE650-CD34-14E5-6B63-42E383FA02BC}.Release|Win32.Build.0 = Release|Win32
|
||||
{B30D70EF-2678-4393-B322-74E1476757DC}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{B30D70EF-2678-4393-B322-74E1476757DC}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{B30D70EF-2678-4393-B322-74E1476757DC}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{B30D70EF-2678-4393-B322-74E1476757DC}.Release|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@ -15,11 +15,11 @@
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
@ -40,7 +40,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
@ -55,7 +55,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
@ -70,20 +70,11 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ol-win.c" />
|
||||
<ClCompile Include="test\echo-demo.c" />
|
||||
<ClCompile Include="test\echo.c" />
|
||||
<ClCompile Include="test\test-ping-pong.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ngx-queue.h" />
|
||||
<ClInclude Include="ol-win.h" />
|
||||
<ClInclude Include="ol.h" />
|
||||
<ClInclude Include="test\echo.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="iocp-links.html" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#include "../ol.h"
|
||||
#include "test.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define BUFSIZE 1024
|
||||
@ -111,7 +111,19 @@ int echo_start(int port) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int echo_stop() {
|
||||
assert(server != NULL);
|
||||
ol_close(server);
|
||||
return ol_close(server);
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(echo_server) {
|
||||
ol_init();
|
||||
if (echo_start(TEST_PORT))
|
||||
return 1;
|
||||
|
||||
fprintf(stderr, "Listening!\n");
|
||||
ol_run();
|
||||
return 0;
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
#include "../ol.h"
|
||||
|
||||
int echo_start(int port);
|
||||
int echo_stop();
|
||||
7
test/test-fail-always.c
Normal file
7
test/test-fail-always.c
Normal file
@ -0,0 +1,7 @@
|
||||
#include "test.h"
|
||||
|
||||
TEST_IMPL(fail_always) {
|
||||
/* This test always fails. It is used to test the test runner. */
|
||||
assert("Yes, it always fails" && 0);
|
||||
return 1;
|
||||
}
|
||||
13
test/test-list.h
Normal file
13
test/test-list.h
Normal file
@ -0,0 +1,13 @@
|
||||
TEST_DECLARE (echo_server)
|
||||
TEST_DECLARE (ping_pong)
|
||||
TEST_DECLARE (pass_always)
|
||||
TEST_DECLARE (fail_always)
|
||||
|
||||
TEST_LIST_START
|
||||
TEST_ENTRY (ping_pong)
|
||||
TEST_HELPER (ping_pong, echo_server)
|
||||
|
||||
TEST_ENTRY (fail_always)
|
||||
|
||||
TEST_ENTRY (pass_always)
|
||||
TEST_LIST_END
|
||||
6
test/test-pass-always.c
Normal file
6
test/test-pass-always.c
Normal file
@ -0,0 +1,6 @@
|
||||
#include "test.h"
|
||||
|
||||
TEST_IMPL(pass_always) {
|
||||
/* This test always passes. It is used to test the test runner. */
|
||||
return 0;
|
||||
}
|
||||
@ -1,11 +1,14 @@
|
||||
#include "../ol.h"
|
||||
#include "echo.h"
|
||||
#include "test.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static int completed_pingers = 0;
|
||||
static ol_req connect_req;
|
||||
|
||||
#define NUM_PINGS 50
|
||||
|
||||
/* 64 bytes is enough for a pinger */
|
||||
#define BUFSIZE 64
|
||||
|
||||
@ -27,11 +30,10 @@ void pinger_on_close(ol_handle* handle, ol_err err) {
|
||||
|
||||
assert(!err);
|
||||
p = (pinger*)handle->data;
|
||||
assert(1000 == p->pongs);
|
||||
assert(NUM_PINGS == p->pongs);
|
||||
free(p);
|
||||
ol_free(handle);
|
||||
completed_pingers++;
|
||||
echo_stop();
|
||||
}
|
||||
|
||||
void pinger_after_read(ol_req* req, size_t nread) {
|
||||
@ -53,7 +55,7 @@ void pinger_after_read(ol_req* req, size_t nread) {
|
||||
p->state = (p->state + 1) % 5;
|
||||
if (p->state == 0) {
|
||||
p->pongs++;
|
||||
if (p->pongs < 1000) {
|
||||
if (p->pongs < NUM_PINGS) {
|
||||
r = ol_write2(p->handle, PING);
|
||||
assert(!r);
|
||||
} else {
|
||||
@ -75,10 +77,16 @@ void pinger_try_read(pinger* pinger) {
|
||||
|
||||
|
||||
void pinger_on_connect(ol_req *req, ol_err err) {
|
||||
int r;
|
||||
ol_handle *handle = req->handle;
|
||||
pinger *p;
|
||||
int r;
|
||||
|
||||
pinger *p = calloc(sizeof(pinger), 1);
|
||||
if (err) {
|
||||
/* error */
|
||||
assert(0);
|
||||
}
|
||||
|
||||
p = calloc(sizeof(pinger), 1);
|
||||
p->handle = handle;
|
||||
p->buf.base = p->read_buffer;
|
||||
p->buf.len = BUFSIZE;
|
||||
@ -97,10 +105,10 @@ void pinger_on_connect(ol_req *req, ol_err err) {
|
||||
|
||||
|
||||
int pinger_connect(int port) {
|
||||
/* Try to connec to the server and do 1000 ping-pongs. */
|
||||
/* Try to connec to the server and do NUM_PINGS ping-pongs. */
|
||||
ol_handle* handle = ol_tcp_handle_new(pinger_on_close, NULL);
|
||||
struct sockaddr_in client_addr = ol_ip4_addr("0.0.0.0", 0);
|
||||
struct sockaddr_in server_addr = ol_ip4_addr("127.0.0.1", port);
|
||||
struct sockaddr_in server_addr = ol_ip4_addr("127.0.0.1", TEST_PORT);
|
||||
|
||||
ol_bind(handle, (struct sockaddr*)&client_addr);
|
||||
ol_req_init(&connect_req, &pinger_on_connect);
|
||||
@ -108,13 +116,9 @@ int pinger_connect(int port) {
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
TEST_IMPL(ping_pong) {
|
||||
ol_init();
|
||||
|
||||
if (echo_start(8000)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pinger_connect(8000)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
210
test/test-runner-win32.c
Normal file
210
test/test-runner-win32.c
Normal file
@ -0,0 +1,210 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "test-runner.h"
|
||||
|
||||
|
||||
int process_start(char *name, process_info_t *p) {
|
||||
HANDLE file = INVALID_HANDLE_VALUE;
|
||||
HANDLE nul = INVALID_HANDLE_VALUE;
|
||||
WCHAR path[MAX_PATH], filename[MAX_PATH];
|
||||
WCHAR image[MAX_PATH + 1];
|
||||
WCHAR args[MAX_PATH * 2];
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
DWORD result;
|
||||
|
||||
if (GetTempPathW(sizeof(path), (WCHAR*)&path) == 0)
|
||||
goto error;
|
||||
if (GetTempFileNameW((WCHAR*)&path, L"ol_", 0, (WCHAR*)&filename) == 0)
|
||||
goto error;
|
||||
|
||||
file = CreateFileW((WCHAR*)filename,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
NULL,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
|
||||
NULL);
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
goto error;
|
||||
|
||||
if (!SetHandleInformation(file, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
|
||||
goto error;
|
||||
|
||||
nul = CreateFileA("nul",
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
if (nul == INVALID_HANDLE_VALUE)
|
||||
goto error;
|
||||
|
||||
if (!SetHandleInformation(nul, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
|
||||
goto error;
|
||||
|
||||
result = GetModuleFileName(NULL, (WCHAR*)&image, sizeof(image));
|
||||
if (result == 0 || result == sizeof(image))
|
||||
goto error;
|
||||
|
||||
if (_snwprintf_s((wchar_t*)&args,
|
||||
sizeof(args) / sizeof(wchar_t),
|
||||
_TRUNCATE,
|
||||
L"\"%s\" %S meh",
|
||||
image,
|
||||
name) < 0)
|
||||
goto error;
|
||||
|
||||
memset((void*)&si, 0, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
si.hStdInput = nul;
|
||||
si.hStdOutput = file;
|
||||
si.hStdError = file;
|
||||
|
||||
if (!CreateProcessW(image, args, NULL, NULL, TRUE,
|
||||
0, NULL, NULL, &si, &pi))
|
||||
goto error;
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
SetHandleInformation(nul, HANDLE_FLAG_INHERIT, 0);
|
||||
SetHandleInformation(file, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
p->stdio_in = nul;
|
||||
p->stdio_out = file;
|
||||
p->process = pi.hProcess;
|
||||
p->name = name;
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
error:
|
||||
if (file != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(file);
|
||||
if (nul != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(file);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Timeout is is msecs. Set timeout < 0 to never time out. */
|
||||
/* Returns 0 when all processes are terminated, -1 on timeout. */
|
||||
int process_wait(process_info_t *vec, int n, int timeout) {
|
||||
int i;
|
||||
HANDLE handles[MAXIMUM_WAIT_OBJECTS];
|
||||
DWORD timeout_api, result;
|
||||
|
||||
/* If there's nothing to wait for, return immedately. */
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
assert(n <= MAXIMUM_WAIT_OBJECTS);
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
handles[i] = vec[i].process;
|
||||
|
||||
if (timeout >= 0) {
|
||||
timeout_api = (DWORD)timeout;
|
||||
} else {
|
||||
timeout_api = INFINITE;
|
||||
}
|
||||
|
||||
result = WaitForMultipleObjects(n, handles, TRUE, timeout_api);
|
||||
|
||||
if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + n) {
|
||||
/* All processes are terminated. */
|
||||
return 0;
|
||||
}
|
||||
if (result == WAIT_TIMEOUT) {
|
||||
return -2;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
long int process_output_size(process_info_t *p) {
|
||||
LARGE_INTEGER size;
|
||||
if (!GetFileSizeEx(p->stdio_out, &size))
|
||||
return -1;
|
||||
return (long int)size.QuadPart;
|
||||
}
|
||||
|
||||
|
||||
int process_copy_output(process_info_t *p, int fd) {
|
||||
/* Any errors in this function are ignored */
|
||||
DWORD read;
|
||||
char buf[1024];
|
||||
|
||||
if (SetFilePointer(p->stdio_out, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
|
||||
return -1;
|
||||
|
||||
while (ReadFile(p->stdio_out, (void*)&buf, sizeof(buf), &read, NULL) &&
|
||||
read > 0)
|
||||
write(fd, buf, read);
|
||||
|
||||
if (GetLastError() != ERROR_HANDLE_EOF)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
char* process_get_name(process_info_t *p) {
|
||||
return p->name;
|
||||
}
|
||||
|
||||
|
||||
int process_terminate(process_info_t *p) {
|
||||
/* If it fails the process is probably already closed. */
|
||||
if (!TerminateProcess(p->process, 1))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int process_reap(process_info_t *p) {
|
||||
DWORD exitCode;
|
||||
if (!GetExitCodeProcess(p->process, &exitCode))
|
||||
return -1;
|
||||
return (int)exitCode;
|
||||
}
|
||||
|
||||
|
||||
void process_cleanup(process_info_t *p) {
|
||||
CloseHandle(p->process);
|
||||
CloseHandle(p->stdio_in);
|
||||
CloseHandle(p->stdio_out);
|
||||
}
|
||||
|
||||
|
||||
int rewind_cursor() {
|
||||
HANDLE handle;
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
COORD coord;
|
||||
|
||||
handle = (HANDLE)_get_osfhandle(fileno(stdout));
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
if (!GetConsoleScreenBufferInfo(handle, &info))
|
||||
return -1;
|
||||
|
||||
coord = info.dwCursorPosition;
|
||||
if (coord.Y <= 0)
|
||||
return -1;
|
||||
|
||||
coord.Y--;
|
||||
coord.X = 0;
|
||||
|
||||
if (!SetConsoleCursorPosition(handle, coord))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
10
test/test-runner-win32.h
Normal file
10
test/test-runner-win32.h
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
HANDLE process;
|
||||
HANDLE stdio_in;
|
||||
HANDLE stdio_out;
|
||||
char *name;
|
||||
} process_info_t;
|
||||
178
test/test-runner.c
Normal file
178
test/test-runner.c
Normal file
@ -0,0 +1,178 @@
|
||||
|
||||
#include "test-runner.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Actual tests and helpers are defined in test-list.h */
|
||||
#include "test-list.h"
|
||||
|
||||
/* The maximum number of processes (main + helpers) that a test can have. */
|
||||
#define TEST_MAX_PROCESSES 8
|
||||
|
||||
/* The time in milliseconds after which a single test times out, */
|
||||
#define TEST_TIMEOUT 20000
|
||||
|
||||
/* Die with fatal error. */
|
||||
#define FATAL(msg) assert(msg && 0);
|
||||
|
||||
/* Log to stderr. */
|
||||
#define LOG(...) fprintf(stderr, "%s", __VA_ARGS__)
|
||||
#define LOGF(...) fprintf(stderr, __VA_ARGS__)
|
||||
|
||||
|
||||
/*
|
||||
* Runs an individual test; returns 1 if the test succeeded, 0 if it failed.
|
||||
* If the test fails it prints diagnostic information.
|
||||
*/
|
||||
int run_test(test_entry_t *test) {
|
||||
int i, result, success;
|
||||
char errmsg[256];
|
||||
test_entry_t *helper;
|
||||
int process_count;
|
||||
process_info_t processes[TEST_MAX_PROCESSES];
|
||||
process_info_t *main_process;
|
||||
|
||||
success = 0;
|
||||
|
||||
process_count = 0;
|
||||
|
||||
/* Start all helpers for this test first */
|
||||
for (helper = (test_entry_t*)&TESTS; helper->main; helper++) {
|
||||
if (helper->is_helper &&
|
||||
strcmp(test->test_name, helper->test_name) == 0) {
|
||||
if (process_start(helper->process_name, &processes[process_count]) == -1) {
|
||||
sprintf_s((char*)&errmsg, sizeof(errmsg), "process `%s` failed to start.", helper->process_name);
|
||||
goto finalize;
|
||||
}
|
||||
process_count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Start the main test process. */
|
||||
if (process_start(test->process_name, &processes[process_count]) == -1) {
|
||||
sprintf_s((char*)&errmsg, sizeof(errmsg), "process `%s` failed to start.", test->process_name);
|
||||
goto finalize;
|
||||
}
|
||||
main_process = &processes[process_count];
|
||||
process_count++;
|
||||
|
||||
/* Wait for the main process to terminate. */
|
||||
result = process_wait(main_process, 1, TEST_TIMEOUT);
|
||||
if (result == -1) {
|
||||
FATAL("process_wait failed\n");
|
||||
} else if (result == -2) {
|
||||
sprintf_s((char*)&errmsg, sizeof(errmsg), "timeout.");
|
||||
goto finalize;
|
||||
}
|
||||
|
||||
/* Reap main process */
|
||||
result = process_reap(main_process);
|
||||
if (result != 0) {
|
||||
sprintf_s((char*)&errmsg, sizeof(errmsg), "exit code %d.", result);
|
||||
goto finalize;
|
||||
}
|
||||
|
||||
/* Yes! did it. */
|
||||
success = 1;
|
||||
|
||||
finalize:
|
||||
/* Kill all (helper) processes that are still running. */
|
||||
for (i = 0; i < process_count; i++)
|
||||
process_terminate(&processes[i]);
|
||||
|
||||
/* Wait until all processes have really terminated. */
|
||||
if (process_wait((process_info_t*)&processes, process_count, -1) < 0)
|
||||
FATAL("process_wait failed\n");
|
||||
|
||||
/* Show error and output from processes if the test failed. */
|
||||
if (!success) {
|
||||
LOG("===============================================================================\n");
|
||||
LOGF("Test `%s` failed: %s\n", test->test_name, errmsg);
|
||||
for (i = 0; i < process_count; i++) {
|
||||
switch (process_output_size(&processes[i])) {
|
||||
case -1:
|
||||
LOGF("Output from process `%s`: << unavailable >>\n", process_get_name(&processes[i]));
|
||||
break;
|
||||
|
||||
case 0:
|
||||
LOGF("Output from process `%s`: << no output >>\n", process_get_name(&processes[i]));
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGF("Output from process `%s`:\n", process_get_name(&processes[i]));
|
||||
process_copy_output(&processes[i], fileno(stderr));
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG("\n");
|
||||
}
|
||||
|
||||
/* Clean up all process handles. */
|
||||
for (i = 0; i < process_count; i++)
|
||||
process_cleanup(&processes[i]);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
void log_progress(int total, int passed, int failed, char *name) {
|
||||
LOGF("[%% %3d|+ %3d|- %3d]: %-50s\n", (passed + failed) / total * 100, passed, failed, name);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int total, passed, failed;
|
||||
test_entry_t *test;
|
||||
|
||||
#ifdef _WIN32
|
||||
/* On windows disable the "application crashed" popup */
|
||||
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
|
||||
#endif
|
||||
|
||||
/* Disable output buffering */
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
|
||||
if (argc > 1) {
|
||||
/* A specific test process is being started. */
|
||||
for (test = (test_entry_t*)&TESTS; test->main; test++) {
|
||||
if (strcmp(argv[1], test->process_name) == 0)
|
||||
return test->main();
|
||||
}
|
||||
LOGF("Test process %s not found!\n", argv[1]);
|
||||
return 255;
|
||||
|
||||
} else {
|
||||
/* Count the number of tests */
|
||||
total = 0;
|
||||
test = (test_entry_t*)&TESTS;
|
||||
for (test = (test_entry_t*)&TESTS; test->main; test++) {
|
||||
if (!test->is_helper)
|
||||
total++;
|
||||
}
|
||||
|
||||
/* Run all tests */
|
||||
passed = 0;
|
||||
failed = 0;
|
||||
test = (test_entry_t*)&TESTS;
|
||||
for (test = (test_entry_t*)&TESTS; test->main; test++) {
|
||||
if (test->is_helper)
|
||||
continue;
|
||||
|
||||
log_progress(total, passed, failed, test->test_name);
|
||||
rewind_cursor();
|
||||
|
||||
if (run_test(test)) {
|
||||
passed++;
|
||||
} else {
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
log_progress(total, passed, failed, "Done.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
83
test/test-runner.h
Normal file
83
test/test-runner.h
Normal file
@ -0,0 +1,83 @@
|
||||
|
||||
#ifndef TEST_RUNNER_H_
|
||||
#define TEST_RUNNER_H_
|
||||
|
||||
/*
|
||||
* Struct to store both tests and to define helper processes for tests.
|
||||
*/
|
||||
typedef struct {
|
||||
char *test_name;
|
||||
char *process_name;
|
||||
int (*main)();
|
||||
int is_helper;
|
||||
} test_entry_t;
|
||||
|
||||
|
||||
/*
|
||||
* Macros used by test-list.h
|
||||
*/
|
||||
#define TEST_DECLARE(name) \
|
||||
int run_##name();
|
||||
|
||||
#define TEST_LIST_START \
|
||||
test_entry_t TESTS[] = {
|
||||
|
||||
#define TEST_LIST_END \
|
||||
{ 0, 0, 0, 0 } \
|
||||
};
|
||||
|
||||
#define TEST_ENTRY(name) \
|
||||
{ #name, #name, &run_##name, 0 },
|
||||
|
||||
#define TEST_HELPER(name, proc) \
|
||||
{ #name, #proc, &run_##proc, 1 },
|
||||
|
||||
|
||||
/*
|
||||
* Include platform-dependent definitions
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
# include "test-runner-win32.h"
|
||||
#else
|
||||
# include "test-runner-unix.h"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Stuff that should be implemented by test-runner-<platform>.h
|
||||
* All functions return 0 on success, -1 on failure, unless specified
|
||||
* otherwise.
|
||||
*/
|
||||
|
||||
/* Invoke "arv[0] test-name". Store process info in *p. */
|
||||
/* Make sure that all stdio output of the processes is buffered up. */
|
||||
int process_start(char *name, process_info_t *p);
|
||||
|
||||
/* Wait for all `n` processes in `vec` to terminate. */
|
||||
/* Time out after `timeout` msec, or never if timeout == -1 */
|
||||
/* Return 0 if all processes are terminated, -1 on error, -2 on timeout. */
|
||||
int process_wait(process_info_t *vec, int n, int timeout);
|
||||
|
||||
/* Returns the number of bytes in the stdio output buffer for process `p`. */
|
||||
long int process_output_size(process_info_t *p);
|
||||
|
||||
/* Copy the contents of the stdio output buffer to `fd`. */
|
||||
int process_copy_output(process_info_t *p, int fd);
|
||||
|
||||
/* Return the name that was specified when `p` was started by process_start */
|
||||
char* process_get_name(process_info_t *p);
|
||||
|
||||
/* Terminate process `p`. */
|
||||
int process_terminate(process_info_t *p);
|
||||
|
||||
/* Return the return value of process p. */
|
||||
/* On error, return -1. */
|
||||
int process_reap(process_info_t *p);
|
||||
|
||||
/* Clean up after terminating process `p` (e.g. free the output buffer etc.) */
|
||||
void process_cleanup(process_info_t *p);
|
||||
|
||||
/* Move the console cursor one line up and back to the first column. */
|
||||
int rewind_cursor();
|
||||
|
||||
#endif /* TEST_RUNNER_H_ */
|
||||
12
test/test.h
Normal file
12
test/test.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef TEST_H_
|
||||
#define TEST_H_
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define TEST_IMPL(name) \
|
||||
int run_##name()
|
||||
|
||||
#define TEST_PORT 8123
|
||||
|
||||
#endif /* TEST_H_ */
|
||||
Loading…
Reference in New Issue
Block a user