mirror of
https://github.com/RGBCube/serenity
synced 2025-07-10 07:27:35 +00:00

As many macros as possible are moved to Macros.h, while the macros to create a test case are moved to TestCase.h. TestCase is now the only user-facing header for creating a test case. TestSuite and its helpers have moved into a .cpp file. Instead of requiring a TEST_MAIN macro to be instantiated into the test file, a TestMain.cpp file is provided instead that will be linked against each test. This has the side effect that, if we wanted to have test cases split across multiple files, it's as simple as adding them all to the same executable. The test main should be portable to kernel mode as well, so if there's a set of tests that should be run in self-test mode in kernel space, we can accomodate that. A new serenity_test CMake function streamlines adding a new test with arguments for the test source file, subdirectory under /usr/Tests to install the test application and an optional list of libraries to link against the test application. To accomodate future test where the provided TestMain.cpp is not suitable (e.g. test-js), a CUSTOM_MAIN parameter can be passed to the function to not link against the boilerplate main function.
164 lines
6.3 KiB
C++
164 lines
6.3 KiB
C++
/*
|
|
* Copyright (c) 2020, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibTest/TestCase.h>
|
|
|
|
#include <AK/ByteBuffer.h>
|
|
#include <AK/Random.h>
|
|
#include <AK/StringBuilder.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
struct Testcase {
|
|
const char* dest;
|
|
size_t dest_n;
|
|
const char* src;
|
|
size_t src_n;
|
|
const char* dest_expected;
|
|
size_t dest_expected_n; // == dest_n
|
|
};
|
|
|
|
static String show(const ByteBuffer& buf)
|
|
{
|
|
StringBuilder builder;
|
|
for (size_t i = 0; i < buf.size(); ++i) {
|
|
builder.appendff("{:02x}", buf[i]);
|
|
}
|
|
builder.append(' ');
|
|
builder.append('(');
|
|
for (size_t i = 0; i < buf.size(); ++i) {
|
|
if (isprint(buf[i]))
|
|
builder.append(buf[i]);
|
|
else
|
|
builder.append('_');
|
|
}
|
|
builder.append(')');
|
|
return builder.build();
|
|
}
|
|
|
|
static const size_t SANDBOX_CANARY_SIZE = 8;
|
|
|
|
static bool test_single(const Testcase& testcase)
|
|
{
|
|
// Preconditions:
|
|
if (testcase.dest_n != testcase.dest_expected_n) {
|
|
warnln("dest length {} != expected dest length {}? Check testcase! (Probably miscounted.)", testcase.dest_n, testcase.dest_expected_n);
|
|
return false;
|
|
}
|
|
if (testcase.src_n != strlen(testcase.src)) {
|
|
warnln("src length {} != actual src length {}? src can't contain NUL bytes!", testcase.src_n, strlen(testcase.src));
|
|
return false;
|
|
}
|
|
|
|
// Setup
|
|
ByteBuffer actual = ByteBuffer::create_uninitialized(SANDBOX_CANARY_SIZE + testcase.dest_n + SANDBOX_CANARY_SIZE);
|
|
fill_with_random(actual.data(), actual.size());
|
|
ByteBuffer expected = actual.isolated_copy();
|
|
VERIFY(actual.offset_pointer(0) != expected.offset_pointer(0));
|
|
actual.overwrite(SANDBOX_CANARY_SIZE, testcase.dest, testcase.dest_n);
|
|
expected.overwrite(SANDBOX_CANARY_SIZE, testcase.dest_expected, testcase.dest_expected_n);
|
|
// "unsigned char" != "char", so we have to convince the compiler to allow this.
|
|
char* dst = reinterpret_cast<char*>(actual.offset_pointer(SANDBOX_CANARY_SIZE));
|
|
|
|
// The actual call:
|
|
size_t actual_return = strlcpy(dst, testcase.src, testcase.dest_n);
|
|
|
|
// Checking the results:
|
|
bool return_ok = actual_return == testcase.src_n;
|
|
bool canary_1_ok = actual.slice(0, SANDBOX_CANARY_SIZE) == expected.slice(0, SANDBOX_CANARY_SIZE);
|
|
bool main_ok = actual.slice(SANDBOX_CANARY_SIZE, testcase.dest_n) == expected.slice(SANDBOX_CANARY_SIZE, testcase.dest_n);
|
|
bool canary_2_ok = actual.slice(SANDBOX_CANARY_SIZE + testcase.dest_n, SANDBOX_CANARY_SIZE) == expected.slice(SANDBOX_CANARY_SIZE + testcase.dest_n, SANDBOX_CANARY_SIZE);
|
|
bool buf_ok = actual == expected;
|
|
|
|
// Evaluate gravity:
|
|
if (buf_ok && (!canary_1_ok || !main_ok || !canary_2_ok)) {
|
|
warnln("Internal error! ({} != {} | {} | {})", buf_ok, canary_1_ok, main_ok, canary_2_ok);
|
|
buf_ok = false;
|
|
}
|
|
if (!canary_1_ok) {
|
|
warnln("Canary 1 overwritten: Expected canary {}\n"
|
|
" instead got {}",
|
|
show(expected.slice(0, SANDBOX_CANARY_SIZE)),
|
|
show(actual.slice(0, SANDBOX_CANARY_SIZE)));
|
|
}
|
|
if (!main_ok) {
|
|
warnln("Wrong output: Expected {}\n"
|
|
" instead got {}",
|
|
show(expected.slice(SANDBOX_CANARY_SIZE, testcase.dest_n)),
|
|
show(actual.slice(SANDBOX_CANARY_SIZE, testcase.dest_n)));
|
|
}
|
|
if (!canary_2_ok) {
|
|
warnln("Canary 2 overwritten: Expected {}\n"
|
|
" instead got {}",
|
|
show(expected.slice(SANDBOX_CANARY_SIZE + testcase.dest_n, SANDBOX_CANARY_SIZE)),
|
|
show(actual.slice(SANDBOX_CANARY_SIZE + testcase.dest_n, SANDBOX_CANARY_SIZE)));
|
|
}
|
|
if (!return_ok) {
|
|
warnln("Wrong return value: Expected {}, got {} instead!", testcase.src_n, actual_return);
|
|
}
|
|
|
|
return buf_ok && return_ok;
|
|
}
|
|
|
|
// Drop the NUL terminator added by the C++ compiler.
|
|
#define LITERAL(x) x, (sizeof(x) - 1)
|
|
|
|
//static Testcase TESTCASES[] = {
|
|
// // Golden path:
|
|
|
|
// // Hitting the border:
|
|
|
|
// // Too long:
|
|
// { LITERAL("Hello World!\0"), LITERAL("Hello Friend!"), LITERAL("Hello Friend\0") },
|
|
// { LITERAL("Hello World!\0"), LITERAL("This source is just *way* too long!"), LITERAL("This source \0") },
|
|
// { LITERAL("x"), LITERAL("This source is just *way* too long!"), LITERAL("\0") },
|
|
// // Other special cases:
|
|
// { LITERAL(""), LITERAL(""), LITERAL("") },
|
|
// { LITERAL(""), LITERAL("Empty test"), LITERAL("") },
|
|
// { LITERAL("x"), LITERAL(""), LITERAL("\0") },
|
|
// { LITERAL("xx"), LITERAL(""), LITERAL("\0x") },
|
|
// { LITERAL("xxx"), LITERAL(""), LITERAL("\0xx") },
|
|
//};
|
|
|
|
TEST_CASE(golden_path)
|
|
{
|
|
EXPECT(test_single({ LITERAL("Hello World!\0\0\0"), LITERAL("Hello Friend!"), LITERAL("Hello Friend!\0\0") }));
|
|
EXPECT(test_single({ LITERAL("Hello World!\0\0\0"), LITERAL("Hello Friend!"), LITERAL("Hello Friend!\0\0") }));
|
|
EXPECT(test_single({ LITERAL("aaaaaaaaaa"), LITERAL("whf"), LITERAL("whf\0aaaaaa") }));
|
|
}
|
|
|
|
TEST_CASE(exact_fit)
|
|
{
|
|
EXPECT(test_single({ LITERAL("Hello World!\0\0"), LITERAL("Hello Friend!"), LITERAL("Hello Friend!\0") }));
|
|
EXPECT(test_single({ LITERAL("AAAA"), LITERAL("aaa"), LITERAL("aaa\0") }));
|
|
}
|
|
|
|
TEST_CASE(off_by_one)
|
|
{
|
|
EXPECT(test_single({ LITERAL("AAAAAAAAAA"), LITERAL("BBBBB"), LITERAL("BBBBB\0AAAA") }));
|
|
EXPECT(test_single({ LITERAL("AAAAAAAAAA"), LITERAL("BBBBBBBCC"), LITERAL("BBBBBBBCC\0") }));
|
|
EXPECT(test_single({ LITERAL("AAAAAAAAAA"), LITERAL("BBBBBBBCCX"), LITERAL("BBBBBBBCC\0") }));
|
|
EXPECT(test_single({ LITERAL("AAAAAAAAAA"), LITERAL("BBBBBBBCCXY"), LITERAL("BBBBBBBCC\0") }));
|
|
}
|
|
|
|
TEST_CASE(nearly_empty)
|
|
{
|
|
EXPECT(test_single({ LITERAL(""), LITERAL(""), LITERAL("") }));
|
|
EXPECT(test_single({ LITERAL(""), LITERAL("Empty test"), LITERAL("") }));
|
|
EXPECT(test_single({ LITERAL("x"), LITERAL(""), LITERAL("\0") }));
|
|
EXPECT(test_single({ LITERAL("xx"), LITERAL(""), LITERAL("\0x") }));
|
|
EXPECT(test_single({ LITERAL("x"), LITERAL("y"), LITERAL("\0") }));
|
|
}
|
|
|
|
static char* const POISON = (char*)1;
|
|
TEST_CASE(to_nullptr)
|
|
{
|
|
EXPECT_EQ(0u, strlcpy(POISON, "", 0));
|
|
EXPECT_EQ(1u, strlcpy(POISON, "x", 0));
|
|
EXPECT(test_single({ LITERAL("Hello World!\0\0\0"), LITERAL("Hello Friend!"), LITERAL("Hello Friend!\0\0") }));
|
|
EXPECT(test_single({ LITERAL("aaaaaaaaaa"), LITERAL("whf"), LITERAL("whf\0aaaaaa") }));
|
|
}
|