Implement a simple test runner for functional tests
This commit is contained in:
parent
145fcddbed
commit
b7015e7b13
6 changed files with 524 additions and 4 deletions
|
|
@ -29,6 +29,9 @@ add_library(apfl
|
|||
add_executable(apfl-bin main.c)
|
||||
target_link_libraries(apfl-bin PUBLIC apfl)
|
||||
|
||||
add_executable(functional-test-runner functional-test-runner.c)
|
||||
target_link_libraries(functional-test-runner PUBLIC apfl)
|
||||
|
||||
set_target_properties(apfl-bin
|
||||
PROPERTIES RUNTIME_OUTPUT_NAME apfl
|
||||
)
|
||||
|
|
@ -43,6 +46,13 @@ unittest(tokenizer_test "")
|
|||
unittest(parser_test "")
|
||||
unittest(resizable_test "resizable.h")
|
||||
unittest(hashmap_test "hashmap.h")
|
||||
unittest(strings_test "")
|
||||
|
||||
function(functionaltest name)
|
||||
add_test(NAME "functionaltest_${name}" COMMAND functional-test-runner ${CMAKE_SOURCE_DIR}/src/functional-tests/${name}.at)
|
||||
endfunction()
|
||||
|
||||
functionaltest("hello-world")
|
||||
|
||||
install(TARGETS apfl DESTINATION lib)
|
||||
install(TARGETS apfl-bin DESTINATION bin)
|
||||
|
|
|
|||
11
src/apfl.h
11
src/apfl.h
|
|
@ -110,10 +110,21 @@ void apfl_string_builder_init(struct apfl_allocator allocator, struct apfl_strin
|
|||
void apfl_string_builder_deinit(struct apfl_string_builder *);
|
||||
bool apfl_string_builder_append(struct apfl_string_builder *, struct apfl_string_view);
|
||||
bool apfl_string_builder_append_byte(struct apfl_string_builder *, char byte);
|
||||
bool apfl_string_builder_append_bytes(struct apfl_string_builder *, const char *bytes, size_t len);
|
||||
struct apfl_string apfl_string_builder_move_string(struct apfl_string_builder *);
|
||||
|
||||
#define apfl_string_builder_append_cstr(builder, cstr) (apfl_string_builder_append((builder), apfl_string_view_from_cstr((cstr))))
|
||||
|
||||
struct apfl_string_view apfl_string_view_offset(struct apfl_string_view sv, size_t off);
|
||||
struct apfl_string_view apfl_string_view_trunc(struct apfl_string_view sv, size_t newlen);
|
||||
struct apfl_string_view apfl_string_view_substr(struct apfl_string_view sv, size_t off, size_t newlen);
|
||||
|
||||
ptrdiff_t apfl_string_view_search(struct apfl_string_view haystack, struct apfl_string_view needle);
|
||||
|
||||
struct apfl_string_view apfl_string_view_ltrim(struct apfl_string_view sv);
|
||||
struct apfl_string_view apfl_string_view_rtrim(struct apfl_string_view sv);
|
||||
struct apfl_string_view apfl_string_view_trim(struct apfl_string_view sv);
|
||||
|
||||
struct apfl_format_writer {
|
||||
bool (*write)(void *, const char *buf, size_t len);
|
||||
void *opaque;
|
||||
|
|
|
|||
243
src/functional-test-runner.c
Normal file
243
src/functional-test-runner.c
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "apfl.h"
|
||||
|
||||
#include "alloc.h"
|
||||
|
||||
enum testresult {
|
||||
T_OK,
|
||||
T_ERR,
|
||||
T_FATAL,
|
||||
};
|
||||
|
||||
struct alloc_context {
|
||||
struct apfl_allocator wrapped;
|
||||
jmp_buf *jmp;
|
||||
};
|
||||
|
||||
struct test_parts {
|
||||
struct apfl_string_view script;
|
||||
struct apfl_string_view output;
|
||||
};
|
||||
|
||||
APFL_NORETURN static void
|
||||
fatal(jmp_buf *jmp, const char *fmt, ...)
|
||||
{
|
||||
va_list varargs;
|
||||
va_start(varargs, fmt);
|
||||
vfprintf(stderr, fmt, varargs);
|
||||
va_end(varargs);
|
||||
|
||||
longjmp(*jmp, 1);
|
||||
}
|
||||
|
||||
static void *
|
||||
alloc_cb(void *opaque, void *oldptr, size_t oldsize, size_t newsize)
|
||||
{
|
||||
struct alloc_context *alloc_ctx = opaque;
|
||||
|
||||
void *out = ALLOCATOR_CALL(alloc_ctx->wrapped, oldptr, oldsize, newsize);
|
||||
|
||||
if (newsize != 0 && out == NULL) {
|
||||
fatal(
|
||||
alloc_ctx->jmp,
|
||||
"Failed to allocate: oldptr=%p, oldsize=%lld, newsize=%lld\n",
|
||||
oldptr,
|
||||
(long long int)oldsize,
|
||||
(long long int)newsize
|
||||
);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
#define BUFSIZE 4096
|
||||
|
||||
static bool
|
||||
file_get_contents(const char *filename, struct apfl_string *str, struct apfl_allocator allocator)
|
||||
{
|
||||
FILE *f = fopen(filename, "r");
|
||||
if (f == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct apfl_string_builder sb;
|
||||
apfl_string_builder_init(allocator, &sb);
|
||||
|
||||
char buf[BUFSIZE];
|
||||
while (!feof(f)) {
|
||||
size_t len = fread(buf, 1, BUFSIZE, f);
|
||||
if (!apfl_string_builder_append_bytes(&sb, buf, len)) {
|
||||
apfl_string_builder_deinit(&sb);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!feof(f)) {
|
||||
apfl_string_builder_deinit(&sb);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
*str = apfl_string_builder_move_string(&sb);
|
||||
apfl_string_builder_deinit(&sb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
parse_test(const struct apfl_string str, struct test_parts *parts)
|
||||
{
|
||||
struct apfl_string_view sv = apfl_string_view_from(str);
|
||||
|
||||
struct apfl_string_view marker_script = apfl_string_view_from("===== script =====\n");
|
||||
struct apfl_string_view marker_output = apfl_string_view_from("===== output =====\n");
|
||||
|
||||
ptrdiff_t script = apfl_string_view_search(sv, marker_script);
|
||||
ptrdiff_t output = apfl_string_view_search(sv, marker_output);
|
||||
|
||||
if (script < 0 || output < 0 || script > output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
parts->script = apfl_string_view_substr(
|
||||
sv,
|
||||
script + marker_script.len,
|
||||
output - script - marker_script.len
|
||||
);
|
||||
parts->output = apfl_string_view_offset(sv, output + marker_output.len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner)
|
||||
{
|
||||
if (apfl_iterative_runner_has_error_on_stack(runner)) {
|
||||
assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stderr)));
|
||||
}
|
||||
}
|
||||
|
||||
enum testresult
|
||||
runtest(const char *filename)
|
||||
{
|
||||
jmp_buf jmp;
|
||||
|
||||
struct alloc_context alloc_ctx = {
|
||||
.wrapped = apfl_allocator_default(),
|
||||
.jmp = &jmp,
|
||||
};
|
||||
|
||||
struct apfl_allocator allocator = {
|
||||
.opaque = &alloc_ctx,
|
||||
.alloc = alloc_cb,
|
||||
};
|
||||
|
||||
if (setjmp(jmp) != 0) {
|
||||
return T_FATAL;
|
||||
}
|
||||
|
||||
struct apfl_string content;
|
||||
if (!file_get_contents(filename, &content, allocator)) {
|
||||
fprintf(stderr, "Could not read file \"%s\"\n", filename);
|
||||
return T_ERR;
|
||||
}
|
||||
|
||||
struct test_parts parts;
|
||||
if (!parse_test(content, &parts)) {
|
||||
fprintf(stderr, "Could not parse test \"%s\"\n", filename);
|
||||
return T_ERR;
|
||||
}
|
||||
|
||||
struct apfl_string_builder output;
|
||||
apfl_string_builder_init(allocator, &output);
|
||||
|
||||
apfl_ctx ctx = apfl_ctx_new((struct apfl_config) {
|
||||
.allocator = allocator,
|
||||
.output_writer = apfl_format_string_writer(&output),
|
||||
});
|
||||
|
||||
struct apfl_string_source_reader_data src_data = apfl_string_source_reader_create(parts.script);
|
||||
apfl_iterative_runner runner = apfl_iterative_runner_new(ctx, apfl_string_source_reader(&src_data));
|
||||
assert(runner != NULL);
|
||||
|
||||
while (apfl_iterative_runner_next(runner)) {
|
||||
switch (apfl_iterative_runner_get_result(runner)) {
|
||||
case APFL_RESULT_OK :
|
||||
break;
|
||||
case APFL_RESULT_ERR:
|
||||
fprintf(stderr, "Error occurred during evaluation.\n");
|
||||
dump_stack_error(ctx, runner);
|
||||
return T_ERR;
|
||||
case APFL_RESULT_ERR_FATAL:
|
||||
fprintf(stderr, "Fatal error occurred during evaluation.\n");
|
||||
dump_stack_error(ctx, runner);
|
||||
return T_FATAL;
|
||||
}
|
||||
}
|
||||
|
||||
apfl_iterative_runner_destroy(runner);
|
||||
apfl_ctx_destroy(ctx);
|
||||
|
||||
struct apfl_string output_string = apfl_string_builder_move_string(&output);
|
||||
struct apfl_string_view have = apfl_string_view_trim(apfl_string_view_from(output_string));
|
||||
struct apfl_string_view want = apfl_string_view_trim(parts.output);
|
||||
|
||||
if (!apfl_string_eq(have, want)) {
|
||||
fprintf(
|
||||
stderr,
|
||||
|
||||
"Test failed\n"
|
||||
"=== WANT ===\n"
|
||||
APFL_STR_FMT "\n"
|
||||
"=== HAVE ===\n"
|
||||
APFL_STR_FMT "\n",
|
||||
|
||||
APFL_STR_FMT_ARGS(want),
|
||||
APFL_STR_FMT_ARGS(have)
|
||||
);
|
||||
|
||||
apfl_string_deinit(allocator, &output_string);
|
||||
apfl_string_deinit(allocator, &content);
|
||||
|
||||
return T_ERR;
|
||||
}
|
||||
|
||||
apfl_string_deinit(allocator, &output_string);
|
||||
apfl_string_deinit(allocator, &content);
|
||||
|
||||
return T_OK;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, const char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "%s: Need at least one argument\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
switch (runtest(argv[i])) {
|
||||
case T_OK:
|
||||
break;
|
||||
case T_ERR:
|
||||
ok = false;
|
||||
break;
|
||||
case T_FATAL:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
5
src/functional-tests/hello-world.at
Normal file
5
src/functional-tests/hello-world.at
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
===== script =====
|
||||
print "Hello World!"
|
||||
|
||||
===== output =====
|
||||
Hello World!
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -117,8 +119,8 @@ apfl_string_builder_deinit(struct apfl_string_builder *builder)
|
|||
builder_reset(builder);
|
||||
}
|
||||
|
||||
static bool
|
||||
append_bytes(struct apfl_string_builder *builder, const char *bytes, size_t len)
|
||||
bool
|
||||
apfl_string_builder_append_bytes(struct apfl_string_builder *builder, const char *bytes, size_t len)
|
||||
{
|
||||
return apfl_resizable_append(
|
||||
builder->allocator,
|
||||
|
|
@ -132,13 +134,13 @@ append_bytes(struct apfl_string_builder *builder, const char *bytes, size_t len)
|
|||
bool
|
||||
apfl_string_builder_append(struct apfl_string_builder *builder, struct apfl_string_view view)
|
||||
{
|
||||
return append_bytes(builder, view.bytes, view.len);
|
||||
return apfl_string_builder_append_bytes(builder, view.bytes, view.len);
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_string_builder_append_byte(struct apfl_string_builder *builder, char byte)
|
||||
{
|
||||
return append_bytes(builder, &byte, 1);
|
||||
return apfl_string_builder_append_bytes(builder, &byte, 1);
|
||||
}
|
||||
|
||||
struct apfl_string
|
||||
|
|
@ -166,3 +168,88 @@ apfl_string_move_into_new_gc_string(struct gc *gc, struct apfl_string *in)
|
|||
*str = apfl_string_move(in);
|
||||
return str;
|
||||
}
|
||||
|
||||
struct apfl_string_view
|
||||
apfl_string_view_offset(struct apfl_string_view sv, size_t off)
|
||||
{
|
||||
if (off >= sv.len) {
|
||||
return (struct apfl_string_view) {
|
||||
.bytes = NULL,
|
||||
.len = 0,
|
||||
};
|
||||
}
|
||||
|
||||
sv.bytes += off;
|
||||
sv.len -= off;
|
||||
|
||||
return sv;
|
||||
}
|
||||
|
||||
struct apfl_string_view
|
||||
apfl_string_view_trunc(struct apfl_string_view sv, size_t newlen)
|
||||
{
|
||||
if (newlen < sv.len) {
|
||||
sv.len = newlen;
|
||||
}
|
||||
return sv;
|
||||
}
|
||||
|
||||
struct apfl_string_view
|
||||
apfl_string_view_substr(struct apfl_string_view sv, size_t off, size_t newlen)
|
||||
{
|
||||
sv = apfl_string_view_offset(sv, off);
|
||||
sv = apfl_string_view_trunc(sv, newlen);
|
||||
return sv;
|
||||
}
|
||||
|
||||
ptrdiff_t
|
||||
apfl_string_view_search(const struct apfl_string_view haystack, const struct apfl_string_view needle)
|
||||
{
|
||||
// TODO: This is not very efficient. Use a faster algorithm here.
|
||||
|
||||
if (needle.len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (needle.len > haystack.len || haystack.len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t lim = haystack.len - needle.len;
|
||||
if (lim > PTRDIFF_MAX) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i <= lim; i++) {
|
||||
if (memcmp(haystack.bytes + i, needle.bytes, needle.len) == 0) {
|
||||
return (ptrdiff_t)i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct apfl_string_view
|
||||
apfl_string_view_ltrim(struct apfl_string_view sv)
|
||||
{
|
||||
while (sv.len > 0 && isspace(sv.bytes[0])) {
|
||||
sv.len--;
|
||||
sv.bytes++;
|
||||
}
|
||||
return sv;
|
||||
}
|
||||
|
||||
struct apfl_string_view
|
||||
apfl_string_view_rtrim(struct apfl_string_view sv)
|
||||
{
|
||||
while (sv.len > 0 && isspace(sv.bytes[sv.len - 1])) {
|
||||
sv.len--;
|
||||
}
|
||||
return sv;
|
||||
}
|
||||
|
||||
struct apfl_string_view
|
||||
apfl_string_view_trim(struct apfl_string_view sv)
|
||||
{
|
||||
return apfl_string_view_ltrim(apfl_string_view_rtrim(sv));
|
||||
}
|
||||
|
|
|
|||
164
src/strings_test.c
Normal file
164
src/strings_test.c
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include "apfl.h"
|
||||
#include "test.h"
|
||||
|
||||
static void
|
||||
search_testcase(testctx t, const char *haystack, const char *needle, ptrdiff_t want)
|
||||
{
|
||||
ptrdiff_t have = apfl_string_view_search(
|
||||
apfl_string_view_from(haystack),
|
||||
apfl_string_view_from(needle)
|
||||
);
|
||||
|
||||
if (have != want) {
|
||||
test_failf(t, "search \"%s\" in \"%s\" failed: want=%d, have=%d", needle, haystack, (int)want, (int)have);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(search, t) {
|
||||
search_testcase(t, "", "", 0);
|
||||
search_testcase(t, "", "foo", -1);
|
||||
search_testcase(t, "foo", "foo", 0);
|
||||
search_testcase(t, "foofoo", "foo", 0);
|
||||
search_testcase(t, "hoofoo", "foo", 3);
|
||||
search_testcase(t, "hoofooo", "foo", 3);
|
||||
search_testcase(t, "ababab", "a", 0);
|
||||
search_testcase(t, "ababab", "b", 1);
|
||||
search_testcase(t, "ababab", "ab", 0);
|
||||
search_testcase(t, "ababab", "ababab", 0);
|
||||
}
|
||||
|
||||
typedef struct apfl_string_view (*trimmer)(struct apfl_string_view);
|
||||
|
||||
static void
|
||||
generic_trim_testcase(testctx t, const char *name, trimmer trim, const char *in, const char *want)
|
||||
{
|
||||
struct apfl_string_view have = trim(apfl_string_view_from(in));
|
||||
if (!apfl_string_eq(want, have)) {
|
||||
test_failf(t, "%s of \"%s\" failed: want=\"%s\", have=\"" APFL_STR_FMT "\"", name, in, want, APFL_STR_FMT_ARGS(have));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ltrim_testcase(testctx t, const char *in, const char *want)
|
||||
{
|
||||
generic_trim_testcase(t, "ltrim", apfl_string_view_ltrim, in, want);
|
||||
}
|
||||
|
||||
static void
|
||||
rtrim_testcase(testctx t, const char *in, const char *want)
|
||||
{
|
||||
generic_trim_testcase(t, "rtrim", apfl_string_view_rtrim, in, want);
|
||||
}
|
||||
|
||||
static void
|
||||
trim_testcase(testctx t, const char *in, const char *want)
|
||||
{
|
||||
generic_trim_testcase(t, "trim", apfl_string_view_trim, in, want);
|
||||
}
|
||||
|
||||
TEST(ltrim, t) {
|
||||
ltrim_testcase(t, "", "");
|
||||
ltrim_testcase(t, " ", "");
|
||||
ltrim_testcase(t, " ", "");
|
||||
ltrim_testcase(t, " \n", "");
|
||||
ltrim_testcase(t, " \t\n", "");
|
||||
ltrim_testcase(t, " foo", "foo");
|
||||
ltrim_testcase(t, " foo ", "foo ");
|
||||
ltrim_testcase(t, "foo ", "foo ");
|
||||
ltrim_testcase(t, "foo\t", "foo\t");
|
||||
ltrim_testcase(t, " f o o ", "f o o ");
|
||||
}
|
||||
|
||||
TEST(rtrim, t) {
|
||||
rtrim_testcase(t, "", "");
|
||||
rtrim_testcase(t, " ", "");
|
||||
rtrim_testcase(t, " ", "");
|
||||
rtrim_testcase(t, " \n", "");
|
||||
rtrim_testcase(t, " \t\n", "");
|
||||
rtrim_testcase(t, " foo", " foo");
|
||||
rtrim_testcase(t, " foo ", " foo");
|
||||
rtrim_testcase(t, "foo ", "foo");
|
||||
rtrim_testcase(t, "foo\t", "foo");
|
||||
rtrim_testcase(t, " f o o ", " f o o");
|
||||
}
|
||||
|
||||
TEST(trim, t) {
|
||||
trim_testcase(t, "", "");
|
||||
trim_testcase(t, " ", "");
|
||||
trim_testcase(t, " ", "");
|
||||
trim_testcase(t, " \n", "");
|
||||
trim_testcase(t, " \t\n", "");
|
||||
trim_testcase(t, " foo", "foo");
|
||||
trim_testcase(t, " foo ", "foo");
|
||||
trim_testcase(t, "foo ", "foo");
|
||||
trim_testcase(t, "foo\t", "foo");
|
||||
trim_testcase(t, " f o o ", "f o o");
|
||||
}
|
||||
|
||||
static void
|
||||
offset_testcase(testctx t, const char *in, size_t off, const char *want)
|
||||
{
|
||||
struct apfl_string_view have = apfl_string_view_offset(apfl_string_view_from(in), off);
|
||||
if (!apfl_string_eq(want, have)) {
|
||||
test_failf(t, "offset(\"%s\", %d) failed: want=\"%s\", have=\"" APFL_STR_FMT "\"", in, (int)off, want, APFL_STR_FMT_ARGS(have));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(offset, t) {
|
||||
offset_testcase(t, "", 0, "");
|
||||
offset_testcase(t, "", 10, "");
|
||||
offset_testcase(t, "foobar", 0, "foobar");
|
||||
offset_testcase(t, "foobar", 1, "oobar");
|
||||
offset_testcase(t, "foobar", 3, "bar");
|
||||
offset_testcase(t, "foobar", 6, "");
|
||||
offset_testcase(t, "foobar", 1000, "");
|
||||
}
|
||||
|
||||
static void
|
||||
trunc_testcase(testctx t, const char *in, size_t newlen, const char *want)
|
||||
{
|
||||
struct apfl_string_view have = apfl_string_view_trunc(apfl_string_view_from(in), newlen);
|
||||
if (!apfl_string_eq(want, have)) {
|
||||
test_failf(t, "trunc(\"%s\", %d) failed: want=\"%s\", have=\"" APFL_STR_FMT "\"", in, (int)newlen, want, APFL_STR_FMT_ARGS(have));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(trunc, t) {
|
||||
trunc_testcase(t, "", 0, "");
|
||||
trunc_testcase(t, "", 10, "");
|
||||
trunc_testcase(t, "foobar", 6, "foobar");
|
||||
trunc_testcase(t, "foobar", 10000, "foobar");
|
||||
trunc_testcase(t, "foobar", 0, "");
|
||||
trunc_testcase(t, "foobar", 5, "fooba");
|
||||
trunc_testcase(t, "foobar", 3, "foo");
|
||||
}
|
||||
|
||||
static void
|
||||
substr_testcase(testctx t, const char *in, size_t off, size_t newlen, const char *want)
|
||||
{
|
||||
struct apfl_string_view have = apfl_string_view_substr(apfl_string_view_from(in), off, newlen);
|
||||
if (!apfl_string_eq(want, have)) {
|
||||
test_failf(t, "substr(\"%s\", %d, %d) failed: want=\"%s\", have=\"" APFL_STR_FMT "\"", in, (int)off, (int)newlen, want, APFL_STR_FMT_ARGS(have));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(substr, t) {
|
||||
substr_testcase(t, "", 0, 0, "");
|
||||
substr_testcase(t, "", 10, 0, "");
|
||||
substr_testcase(t, "", 0, 10, "");
|
||||
substr_testcase(t, "foobar", 0, 10, "foobar");
|
||||
substr_testcase(t, "foobar", 0, 6, "foobar");
|
||||
substr_testcase(t, "foobar", 0, 5, "fooba");
|
||||
substr_testcase(t, "foobar", 1, 5, "oobar");
|
||||
substr_testcase(t, "foobar", 100, 5, "");
|
||||
}
|
||||
|
||||
TESTS_BEGIN
|
||||
ADDTEST(search),
|
||||
ADDTEST(ltrim),
|
||||
ADDTEST(rtrim),
|
||||
ADDTEST(trim),
|
||||
ADDTEST(offset),
|
||||
ADDTEST(trunc),
|
||||
ADDTEST(substr),
|
||||
TESTS_END
|
||||
Loading…
Reference in a new issue