http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestString.c ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/TestString.c b/runtime/test/Clownfish/Test/TestString.c new file mode 100644 index 0000000..d89b5fe --- /dev/null +++ b/runtime/test/Clownfish/Test/TestString.c @@ -0,0 +1,848 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> +#include <stdio.h> + +#define CFISH_USE_SHORT_NAMES +#define TESTCFISH_USE_SHORT_NAMES + +#include "Clownfish/Test/TestString.h" + +#include "Clownfish/String.h" +#include "Clownfish/Boolean.h" +#include "Clownfish/ByteBuf.h" +#include "Clownfish/CharBuf.h" +#include "Clownfish/Err.h" +#include "Clownfish/Test.h" +#include "Clownfish/TestHarness/TestBatchRunner.h" +#include "Clownfish/TestHarness/TestUtils.h" +#include "Clownfish/Util/Memory.h" +#include "Clownfish/Class.h" + +#define SMILEY "\xE2\x98\xBA" +static char smiley[] = { (char)0xE2, (char)0x98, (char)0xBA, 0 }; +static uint32_t smiley_len = 3; +static int32_t smiley_cp = 0x263A; + +TestString* +TestStr_new() { + return (TestString*)Class_Make_Obj(TESTSTRING); +} + +static String* +S_get_str(const char *string) { + return Str_new_from_utf8(string, strlen(string)); +} + +// Surround a smiley with lots of whitespace. +static String* +S_smiley_with_whitespace(size_t *num_spaces_ptr) { + int32_t spaces[] = { + ' ', '\t', '\r', '\n', 0x000B, 0x000C, 0x000D, 0x0085, + 0x00A0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, + 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x2028, 0x2029, 0x202F, + 0x205F, 0x3000 + }; + size_t num_spaces = sizeof(spaces) / sizeof(uint32_t); + + CharBuf *buf = CB_new(0); + for (size_t i = 0; i < num_spaces; i++) { CB_Cat_Char(buf, spaces[i]); } + CB_Cat_Char(buf, smiley_cp); + for (size_t i = 0; i < num_spaces; i++) { CB_Cat_Char(buf, spaces[i]); } + + String *retval = CB_To_String(buf); + if (num_spaces_ptr) { *num_spaces_ptr = num_spaces; } + + DECREF(buf); + return retval; +} + +static void +test_new(TestBatchRunner *runner) { + static char chars[] = "A string " SMILEY " with a smile."; + + { + char *buffer = (char*)MALLOCATE(sizeof(chars)); + strcpy(buffer, chars); + String *thief = Str_new_steal_utf8(buffer, sizeof(chars) - 1); + TEST_TRUE(runner, Str_Equals_Utf8(thief, chars, sizeof(chars) - 1), + "Str_new_steal_utf8"); + DECREF(thief); + } + + { + char *buffer = (char*)MALLOCATE(sizeof(chars)); + strcpy(buffer, chars); + String *thief + = Str_new_steal_trusted_utf8(buffer, sizeof(chars) - 1); + TEST_TRUE(runner, Str_Equals_Utf8(thief, chars, sizeof(chars) - 1), + "Str_new_steal_trusted_utf8"); + DECREF(thief); + } + + { + String *wrapper = Str_new_wrap_utf8(chars, sizeof(chars) - 1); + TEST_TRUE(runner, Str_Equals_Utf8(wrapper, chars, sizeof(chars) - 1), + "Str_new_wrap_utf8"); + DECREF(wrapper); + } + + { + String *wrapper = Str_new_wrap_trusted_utf8(chars, sizeof(chars) - 1); + TEST_TRUE(runner, Str_Equals_Utf8(wrapper, chars, sizeof(chars) - 1), + "Str_new_wrap_trusted_utf8"); + DECREF(wrapper); + } + + { + String *smiley_str = Str_new_from_char(smiley_cp); + TEST_TRUE(runner, Str_Equals_Utf8(smiley_str, smiley, smiley_len), + "Str_new_from_char"); + DECREF(smiley_str); + } +} + +static void +test_Cat(TestBatchRunner *runner) { + String *wanted = Str_newf("a%s", smiley); + String *source; + String *got; + + source = S_get_str(""); + got = Str_Cat(source, wanted); + TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat"); + DECREF(got); + DECREF(source); + + source = S_get_str("a"); + got = Str_Cat_Utf8(source, smiley, smiley_len); + TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat_Utf8"); + DECREF(got); + DECREF(source); + + source = S_get_str("a"); + got = Str_Cat_Trusted_Utf8(source, smiley, smiley_len); + TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat_Trusted_Utf8"); + DECREF(got); + DECREF(source); + + DECREF(wanted); +} + +static void +test_Clone(TestBatchRunner *runner) { + String *wanted = S_get_str("foo"); + String *got = Str_Clone(wanted); + TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Clone"); + DECREF(got); + DECREF(wanted); +} + +static int64_t +S_find(String *string, String *substring) { + StringIterator *iter = Str_Find(string, substring); + if (iter == NULL) { return -1; } + size_t tick = StrIter_Recede(iter, SIZE_MAX); + DECREF(iter); + return (int64_t)tick; +} + +static void +test_Contains_and_Find(TestBatchRunner *runner) { + String *string; + String *substring = S_get_str("foo"); + String *empty = S_get_str(""); + + TEST_FALSE(runner, Str_Contains(empty, substring), + "Not contained in empty string"); + TEST_INT_EQ(runner, S_find(empty, substring), -1, + "Not found in empty string"); + + string = S_get_str("foo"); + TEST_TRUE(runner, Str_Contains(string, substring), + "Contains complete string"); + TEST_INT_EQ(runner, S_find(string, substring), 0, "Find complete string"); + TEST_TRUE(runner, Str_Contains(string, empty), + "Contains empty string"); + TEST_INT_EQ(runner, S_find(string, empty), 0, "Find empty string"); + DECREF(string); + + string = S_get_str("afoo"); + TEST_TRUE(runner, Str_Contains(string, substring), + "Contained after first"); + TEST_INT_EQ(runner, S_find(string, substring), 1, "Find after first"); + String *prefix = Str_SubString(string, 0, 3); + TEST_FALSE(runner, Str_Contains(prefix, substring), "Don't overrun"); + DECREF(prefix); + DECREF(string); + + string = S_get_str("afood"); + TEST_TRUE(runner, Str_Contains(string, substring), "Contained in middle"); + TEST_INT_EQ(runner, S_find(string, substring), 1, "Find in middle"); + DECREF(string); + + DECREF(empty); + DECREF(substring); +} + +static void +test_Code_Point_At_and_From(TestBatchRunner *runner) { + int32_t code_points[] = { + 'a', smiley_cp, smiley_cp, 'b', smiley_cp, 'c' + }; + uint32_t num_code_points = sizeof(code_points) / sizeof(int32_t); + String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley); + uint32_t i; + + for (i = 0; i < num_code_points; i++) { + uint32_t from = num_code_points - i; + TEST_INT_EQ(runner, Str_Code_Point_At(string, i), code_points[i], + "Code_Point_At %ld", (long)i); + TEST_INT_EQ(runner, Str_Code_Point_From(string, from), + code_points[i], "Code_Point_From %ld", (long)from); + } + + TEST_INT_EQ(runner, Str_Code_Point_At(string, num_code_points), STR_OOB, + "Code_Point_At %ld", (long)num_code_points); + TEST_INT_EQ(runner, Str_Code_Point_From(string, 0), STR_OOB, + "Code_Point_From 0"); + TEST_INT_EQ(runner, Str_Code_Point_From(string, num_code_points + 1), + STR_OOB, "Code_Point_From %ld", (long)(num_code_points + 1)); + + DECREF(string); +} + +static void +test_SubString(TestBatchRunner *runner) { + { + String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley); + String *wanted = Str_newf("%sb%s", smiley, smiley); + String *got = Str_SubString(string, 2, 3); + TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "SubString"); + DECREF(string); + DECREF(wanted); + DECREF(got); + } + + { + static const char chars[] = "A string."; + String *wrapper = Str_new_wrap_utf8(chars, sizeof(chars) - 1); + String *wanted = Str_newf("string"); + String *got = Str_SubString(wrapper, 2, 6); + TEST_TRUE(runner, Str_Equals(got, (Obj*)wanted), + "SubString with wrapped buffer"); + DECREF(wrapper); + DECREF(wanted); + DECREF(got); + } +} + +static void +test_Trim(TestBatchRunner *runner) { + String *ws_smiley = S_smiley_with_whitespace(NULL); + String *ws_foo = S_get_str(" foo "); + String *ws_only = S_get_str(" \t \r\n"); + String *trimmed = S_get_str("a b"); + String *got; + + got = Str_Trim(ws_smiley); + TEST_TRUE(runner, Str_Equals_Utf8(got, smiley, smiley_len), "Trim"); + DECREF(got); + + got = Str_Trim_Top(ws_foo); + TEST_TRUE(runner, Str_Equals_Utf8(got, "foo ", 5), "Trim_Top"); + DECREF(got); + + got = Str_Trim_Tail(ws_foo); + TEST_TRUE(runner, Str_Equals_Utf8(got, " foo", 5), "Trim_Tail"); + DECREF(got); + + got = Str_Trim(ws_only); + TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0), "Trim with only whitespace"); + DECREF(got); + + got = Str_Trim_Top(ws_only); + TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0), + "Trim_Top with only whitespace"); + DECREF(got); + + got = Str_Trim_Tail(ws_only); + TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0), + "Trim_Tail with only whitespace"); + DECREF(got); + + got = Str_Trim(trimmed); + TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed), + "Trim doesn't change trimmed string"); + DECREF(got); + + got = Str_Trim_Top(trimmed); + TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed), + "Trim_Top doesn't change trimmed string"); + DECREF(got); + + got = Str_Trim_Tail(trimmed); + TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed), + "Trim_Tail doesn't change trimmed string"); + DECREF(got); + + DECREF(trimmed); + DECREF(ws_only); + DECREF(ws_foo); + DECREF(ws_smiley); +} + +static void +test_To_F64(TestBatchRunner *runner) { + String *string; + + string = S_get_str("1.5"); + double difference = 1.5 - Str_To_F64(string); + if (difference < 0) { difference = 0 - difference; } + TEST_TRUE(runner, difference < 0.001, "To_F64"); + DECREF(string); + + string = S_get_str("-1.5"); + difference = 1.5 + Str_To_F64(string); + if (difference < 0) { difference = 0 - difference; } + TEST_TRUE(runner, difference < 0.001, "To_F64 negative"); + DECREF(string); + + // TODO: Enable this test when we have real substrings. + /*string = S_get_str("1.59"); + double value_full = Str_To_F64(string); + Str_Set_Size(string, 3); + double value_short = Str_To_F64(string); + TEST_TRUE(runner, value_short < value_full, + "TO_F64 doesn't run past end of string"); + DECREF(string);*/ +} + +static void +test_To_I64(TestBatchRunner *runner) { + String *string; + + string = S_get_str("10"); + TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64"); + DECREF(string); + + string = S_get_str("-10"); + TEST_INT_EQ(runner, Str_To_I64(string), -10, "To_I64 negative"); + DECREF(string); + + string = S_get_str("10."); + TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64 stops at non-digits"); + DECREF(string); + + string = S_get_str("10" SMILEY); + TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64 stops at non-ASCII"); + DECREF(string); + + string = S_get_str("10A"); + TEST_INT_EQ(runner, Str_To_I64(string), 10, + "To_I64 stops at out-of-range digits"); + DECREF(string); +} + +static void +test_BaseX_To_I64(TestBatchRunner *runner) { + String *string; + + string = S_get_str("-JJ"); + TEST_INT_EQ(runner, Str_BaseX_To_I64(string, 20), -399, + "BaseX_To_I64 base 20"); + DECREF(string); +} + +static void +test_To_String(TestBatchRunner *runner) { + String *string = Str_newf("Test"); + String *copy = Str_To_String(string); + TEST_TRUE(runner, Str_Equals(copy, (Obj*)string), "To_String"); + DECREF(string); + DECREF(copy); +} + +static void +test_To_Utf8(TestBatchRunner *runner) { + String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley); + char *buf = Str_To_Utf8(string); + TEST_TRUE(runner, strcmp(buf, "a" SMILEY SMILEY "b" SMILEY "c") == 0, + "To_Utf8"); + FREEMEM(buf); + DECREF(string); +} + +static void +test_To_ByteBuf(TestBatchRunner *runner) { + String *string = Str_newf("foo"); + ByteBuf *bb = Str_To_ByteBuf(string); + TEST_TRUE(runner, BB_Equals_Bytes(bb, "foo", 3), "To_ByteBuf"); + DECREF(bb); + DECREF(string); +} + +static void +test_Length(TestBatchRunner *runner) { + String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley); + TEST_UINT_EQ(runner, Str_Length(string), 6, "Length"); + DECREF(string); +} + +static void +test_Compare_To(TestBatchRunner *runner) { + String *abc = Str_newf("a%s%sb%sc", smiley, smiley, smiley); + String *ab = Str_newf("a%s%sb", smiley, smiley); + String *ac = Str_newf("a%s%sc", smiley, smiley); + + TEST_TRUE(runner, Str_Compare_To(abc, (Obj*)abc) == 0, + "Compare_To abc abc"); + TEST_TRUE(runner, Str_Compare_To(ab, (Obj*)abc) < 0, + "Compare_To ab abc"); + TEST_TRUE(runner, Str_Compare_To(abc, (Obj*)ab) > 0, + "Compare_To abc ab"); + TEST_TRUE(runner, Str_Compare_To(ab, (Obj*)ac) < 0, + "Compare_To ab ac"); + TEST_TRUE(runner, Str_Compare_To(ac, (Obj*)ab) > 0, + "Compare_To ac ab"); + + DECREF(ac); + DECREF(ab); + DECREF(abc); +} + +static void +test_Starts_Ends_With(TestBatchRunner *runner) { + String *prefix = S_get_str("pre" SMILEY "fix_"); + String *suffix = S_get_str("_post" SMILEY "fix"); + String *empty = S_get_str(""); + + TEST_TRUE(runner, Str_Starts_With(suffix, suffix), + "Starts_With self returns true"); + TEST_TRUE(runner, Str_Ends_With(prefix, prefix), + "Ends_With self returns true"); + + TEST_TRUE(runner, Str_Starts_With(suffix, empty), + "Starts_With empty string returns true"); + TEST_TRUE(runner, Str_Ends_With(prefix, empty), + "Ends_With empty string returns true"); + TEST_FALSE(runner, Str_Starts_With(empty, suffix), + "Empty string Starts_With returns false"); + TEST_FALSE(runner, Str_Ends_With(empty, prefix), + "Empty string Ends_With returns false"); + + { + String *string + = S_get_str("pre" SMILEY "fix_string_post" SMILEY "fix"); + TEST_TRUE(runner, Str_Starts_With(string, prefix), + "Starts_With returns true"); + TEST_TRUE(runner, Str_Ends_With(string, suffix), + "Ends_With returns true"); + DECREF(string); + } + + { + String *string + = S_get_str("pre" SMILEY "fix:string:post" SMILEY "fix"); + TEST_FALSE(runner, Str_Starts_With(string, prefix), + "Starts_With returns false"); + TEST_FALSE(runner, Str_Ends_With(string, suffix), + "Ends_With returns false"); + DECREF(string); + } + + DECREF(prefix); + DECREF(suffix); + DECREF(empty); +} + +static void +test_Starts_Ends_With_Utf8(TestBatchRunner *runner) { + String *str = S_get_str("pre" SMILEY "post"); + + static const char prefix[] = "pre" SMILEY; + static const char postfix[] = SMILEY "post"; + static const size_t prefix_size = sizeof(prefix) - 1; + static const size_t postfix_size = sizeof(postfix) - 1; + TEST_TRUE(runner, Str_Starts_With_Utf8(str, prefix, prefix_size), + "Starts_With_Utf8 returns true"); + TEST_TRUE(runner, Str_Ends_With_Utf8(str, postfix, postfix_size), + "Ends_With_Utf8 returns true"); + TEST_FALSE(runner, Str_Starts_With_Utf8(str, postfix, postfix_size), + "Starts_With_Utf8 returns false"); + TEST_FALSE(runner, Str_Ends_With_Utf8(str, prefix, prefix_size), + "Ends_With_Utf8 returns false"); + + static const char longer[] = "12345678901234567890"; + static const size_t longer_size = sizeof(longer) - 1; + TEST_FALSE(runner, Str_Starts_With_Utf8(str, longer, longer_size), + "Starts_With_Utf8 longer str returns false"); + TEST_FALSE(runner, Str_Ends_With_Utf8(str, longer, longer_size), + "Ends_With_Utf8 longer str returns false"); + + DECREF(str); +} + +static void +test_Get_Ptr8(TestBatchRunner *runner) { + String *string = S_get_str("Banana"); + + const char *ptr8 = Str_Get_Ptr8(string); + TEST_TRUE(runner, strcmp(ptr8, "Banana") == 0, "Get_Ptr8"); + + size_t size = Str_Get_Size(string); + TEST_UINT_EQ(runner, size, 6, "Get_Size"); + + DECREF(string); +} + +static void +test_iterator(TestBatchRunner *runner) { + static const int32_t code_points[] = { + 0x41, + 0x7F, + 0x80, + 0x7FF, + 0x800, + 0xFFFF, + 0x10000, + 0x10FFFF + }; + static size_t num_code_points + = sizeof(code_points) / sizeof(code_points[0]); + + CharBuf *buf = CB_new(0); + for (size_t i = 0; i < num_code_points; ++i) { + CB_Cat_Char(buf, code_points[i]); + } + String *string = CB_To_String(buf); + + { + StringIterator *iter = Str_Top(string); + + TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)iter), + "StringIterator equal to self"); + TEST_FALSE(runner, StrIter_Equals(iter, (Obj*)CFISH_TRUE), + "StringIterator not equal non-iterators"); + + DECREF(iter); + } + + { + StringIterator *top = Str_Top(string); + StringIterator *tail = Str_Tail(string); + + TEST_FALSE(runner, StrIter_Equals(top, (Obj*)tail), + "StrIter_Equals returns false"); + + TEST_INT_EQ(runner, StrIter_Compare_To(top, (Obj*)tail), -1, + "Compare_To top < tail"); + TEST_INT_EQ(runner, StrIter_Compare_To(tail, (Obj*)top), 1, + "Compare_To tail > top"); + TEST_INT_EQ(runner, StrIter_Compare_To(top, (Obj*)top), 0, + "Compare_To top == top"); + + StringIterator *clone = StrIter_Clone(top); + TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)top), "Clone"); + + StrIter_Assign(clone, tail); + TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)tail), "Assign"); + + String *other = Str_newf("Other string"); + StringIterator *other_iter = Str_Top(other); + TEST_FALSE(runner, StrIter_Equals(other_iter, (Obj*)tail), + "Equals returns false for different strings"); + StrIter_Assign(clone, other_iter); + TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)other_iter), + "Assign iterator with different string"); + + DECREF(other); + DECREF(other_iter); + DECREF(clone); + DECREF(top); + DECREF(tail); + } + + { + StringIterator *iter = Str_Top(string); + + for (size_t i = 0; i < num_code_points; ++i) { + TEST_TRUE(runner, StrIter_Has_Next(iter), "Has_Next %d", i); + int32_t code_point = StrIter_Next(iter); + TEST_INT_EQ(runner, code_point, code_points[i], "Next %d", i); + } + + TEST_TRUE(runner, !StrIter_Has_Next(iter), + "Has_Next at end of string"); + TEST_INT_EQ(runner, StrIter_Next(iter), STR_OOB, + "Next at end of string"); + + StringIterator *tail = Str_Tail(string); + TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)tail), "Equals tail"); + + DECREF(tail); + DECREF(iter); + } + + { + StringIterator *iter = Str_Tail(string); + + for (size_t i = num_code_points; i--;) { + TEST_TRUE(runner, StrIter_Has_Prev(iter), "Has_Prev %d", i); + int32_t code_point = StrIter_Prev(iter); + TEST_INT_EQ(runner, code_point, code_points[i], "Prev %d", i); + } + + TEST_TRUE(runner, !StrIter_Has_Prev(iter), + "Has_Prev at end of string"); + TEST_INT_EQ(runner, StrIter_Prev(iter), STR_OOB, + "Prev at start of string"); + + StringIterator *top = Str_Top(string); + TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)top), "Equals top"); + + DECREF(top); + DECREF(iter); + } + + { + StringIterator *iter = Str_Top(string); + + StrIter_Next(iter); + TEST_UINT_EQ(runner, StrIter_Advance(iter, 2), 2, + "Advance returns number of code points"); + TEST_INT_EQ(runner, StrIter_Next(iter), code_points[3], + "Advance works"); + TEST_UINT_EQ(runner, + StrIter_Advance(iter, 1000000), num_code_points - 4, + "Advance past end of string"); + + StrIter_Prev(iter); + TEST_UINT_EQ(runner, StrIter_Recede(iter, 2), 2, + "Recede returns number of code points"); + TEST_INT_EQ(runner, StrIter_Prev(iter), code_points[num_code_points-4], + "Recede works"); + TEST_UINT_EQ(runner, StrIter_Recede(iter, 1000000), num_code_points - 4, + "Recede past start of string"); + + DECREF(iter); + } + + DECREF(string); + DECREF(buf); +} + +static void +test_iterator_whitespace(TestBatchRunner *runner) { + size_t num_spaces; + String *ws_smiley = S_smiley_with_whitespace(&num_spaces); + + { + StringIterator *iter = Str_Top(ws_smiley); + TEST_UINT_EQ(runner, StrIter_Skip_Whitespace(iter), num_spaces, + "Skip_Whitespace"); + TEST_UINT_EQ(runner, StrIter_Skip_Whitespace(iter), 0, + "Skip_Whitespace without whitespace"); + DECREF(iter); + } + + { + StringIterator *iter = Str_Tail(ws_smiley); + TEST_UINT_EQ(runner, StrIter_Skip_Whitespace_Back(iter), num_spaces, + "Skip_Whitespace_Back"); + TEST_UINT_EQ(runner, StrIter_Skip_Whitespace_Back(iter), 0, + "Skip_Whitespace_Back without whitespace"); + DECREF(iter); + } + + DECREF(ws_smiley); +} + +typedef struct { + StringIterator *top; + StringIterator *tail; +} StrIterCropContext; + +static void +S_striter_crop(void *vcontext) { + StrIterCropContext *context = (StrIterCropContext*)vcontext; + StrIter_crop(context->top, context->tail); +} + +static void +test_iterator_substring(TestBatchRunner *runner) { + String *string = Str_newf("a%sb%sc%sd", smiley, smiley, smiley); + + StringIterator *start = Str_Top(string); + StringIterator *end = Str_Tail(string); + + { + String *substring = StrIter_crop(start, end); + TEST_TRUE(runner, Str_Equals(substring, (Obj*)string), + "StrIter_crop whole string"); + DECREF(substring); + } + + StrIter_Advance(start, 2); + StrIter_Recede(end, 2); + + { + String *substring = StrIter_crop(start, end); + static const char wanted_buf[] = "b" SMILEY "c"; + static const size_t wanted_size = sizeof(wanted_buf) - 1; + String *wanted = Str_new_from_utf8(wanted_buf, wanted_size); + TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted), + "StrIter_crop"); + + TEST_TRUE(runner, StrIter_Starts_With(start, wanted), + "Starts_With returns true"); + TEST_TRUE(runner, StrIter_Ends_With(end, wanted), + "Ends_With returns true"); + TEST_TRUE(runner, + StrIter_Starts_With_Utf8(start, wanted_buf, wanted_size), + "Starts_With_Utf8 returns true"); + TEST_TRUE(runner, + StrIter_Ends_With_Utf8(end, wanted_buf, wanted_size), + "Ends_With_Utf8 returns true"); + + DECREF(wanted); + DECREF(substring); + } + + { + static const char short_buf[] = "b" SMILEY "x"; + static const size_t short_size = sizeof(short_buf) - 1; + String *short_str = Str_new_from_utf8(short_buf, short_size); + TEST_FALSE(runner, StrIter_Starts_With(start, short_str), + "Starts_With returns false"); + TEST_FALSE(runner, StrIter_Ends_With(start, short_str), + "Ends_With returns false"); + TEST_FALSE(runner, + StrIter_Starts_With_Utf8(start, short_buf, short_size), + "Starts_With_Utf8 returns false"); + TEST_FALSE(runner, + StrIter_Ends_With_Utf8(start, short_buf, short_size), + "Ends_With_Utf8 returns false"); + + static const char long_buf[] = "b" SMILEY "xxxxxxxxxxxx" SMILEY "c"; + static const size_t long_size = sizeof(long_buf) - 1; + String *long_str = Str_new_from_utf8(long_buf, long_size); + TEST_FALSE(runner, StrIter_Starts_With(start, long_str), + "Starts_With long string returns false"); + TEST_FALSE(runner, StrIter_Ends_With(end, long_str), + "Ends_With long string returns false"); + TEST_FALSE(runner, + StrIter_Starts_With_Utf8(start, long_buf, long_size), + "Starts_With_Utf8 long string returns false"); + TEST_FALSE(runner, + StrIter_Ends_With_Utf8(end, long_buf, long_size), + "Ends_With_Utf8 long string returns false"); + + DECREF(short_str); + DECREF(long_str); + } + + { + String *substring = StrIter_crop(end, NULL); + String *wanted = Str_newf("%sd", smiley); + TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted), + "StrIter_crop with NULL tail"); + DECREF(wanted); + DECREF(substring); + } + + { + String *substring = StrIter_crop(NULL, start); + String *wanted = Str_newf("a%s", smiley); + TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted), + "StrIter_crop with NULL top"); + DECREF(wanted); + DECREF(substring); + } + + { + StrIterCropContext context; + context.top = NULL; + context.tail = NULL; + Err *error = Err_trap(S_striter_crop, &context); + TEST_TRUE(runner, error != NULL, + "StrIter_crop throws if top and tail are NULL"); + DECREF(error); + } + + { + String *other = SSTR_WRAP_C("other"); + StrIterCropContext context; + context.top = start; + context.tail = Str_Tail(other); + Err *error = Err_trap(S_striter_crop, &context); + TEST_TRUE(runner, error != NULL, + "StrIter_crop throws if string don't match"); + DECREF(error); + DECREF(context.tail); + } + + { + StrIterCropContext context; + context.top = end; + context.tail = start; + Err *error = Err_trap(S_striter_crop, &context); + TEST_TRUE(runner, error != NULL, + "StrIter_crop throws if top is behind tail"); + DECREF(error); + } + + DECREF(start); + DECREF(end); + DECREF(string); +} + +void +TestStr_Run_IMP(TestString *self, TestBatchRunner *runner) { + TestBatchRunner_Plan(runner, (TestBatch*)self, 158); + test_new(runner); + test_Cat(runner); + test_Clone(runner); + test_Code_Point_At_and_From(runner); + test_Contains_and_Find(runner); + test_SubString(runner); + test_Trim(runner); + test_To_F64(runner); + test_To_I64(runner); + test_BaseX_To_I64(runner); + test_To_String(runner); + test_To_Utf8(runner); + test_To_ByteBuf(runner); + test_Length(runner); + test_Compare_To(runner); + test_Starts_Ends_With(runner); + test_Starts_Ends_With_Utf8(runner); + test_Get_Ptr8(runner); + test_iterator(runner); + test_iterator_whitespace(runner); + test_iterator_substring(runner); +} + +/*************************** StringCallbackTest ***************************/ + +bool +StrCbTest_Unchanged_By_Callback_IMP(StringCallbackTest *self, String *str) { + String *before = Str_Clone(str); + StrCbTest_Callback(self); + return Str_Equals(str, (Obj*)before); +} +
http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestString.cfh ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/TestString.cfh b/runtime/test/Clownfish/Test/TestString.cfh new file mode 100644 index 0000000..88e43c9 --- /dev/null +++ b/runtime/test/Clownfish/Test/TestString.cfh @@ -0,0 +1,36 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +parcel TestClownfish; + +class Clownfish::Test::TestString nickname TestStr + inherits Clownfish::TestHarness::TestBatch { + + inert incremented TestString* + new(); + + void + Run(TestString *self, TestBatchRunner *runner); +} + +abstract class Clownfish::Test::StringCallbackTest nickname StrCbTest { + bool + Unchanged_By_Callback(StringCallbackTest *self, String *str); + + abstract void + Callback(StringCallbackTest *self); +} + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestVector.c ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/TestVector.c b/runtime/test/Clownfish/Test/TestVector.c new file mode 100644 index 0000000..17a7f98 --- /dev/null +++ b/runtime/test/Clownfish/Test/TestVector.c @@ -0,0 +1,572 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> +#include <stdlib.h> + +#define C_CFISH_VECTOR +#define CFISH_USE_SHORT_NAMES +#define TESTCFISH_USE_SHORT_NAMES + +#define MAX_VECTOR_SIZE (SIZE_MAX / sizeof(Obj*)) + +#include "Clownfish/Test/TestVector.h" + +#include "Clownfish/String.h" +#include "Clownfish/Boolean.h" +#include "Clownfish/Err.h" +#include "Clownfish/Num.h" +#include "Clownfish/Test.h" +#include "Clownfish/TestHarness/TestBatchRunner.h" +#include "Clownfish/TestHarness/TestUtils.h" +#include "Clownfish/Vector.h" +#include "Clownfish/Class.h" + +TestVector* +TestVector_new() { + return (TestVector*)Class_Make_Obj(TESTVECTOR); +} + +// Return an array of size 10 with 30 garbage pointers behind. +static Vector* +S_array_with_garbage() { + Vector *array = Vec_new(100); + + for (int i = 0; i < 40; i++) { + Vec_Push(array, (Obj*)CFISH_TRUE); + } + + // Remove elements using different methods. + Vec_Excise(array, 10, 10); + for (int i = 0; i < 10; i++) { Vec_Pop(array); } + Vec_Resize(array, 10); + + return array; +} + +static void +test_Equals(TestBatchRunner *runner) { + Vector *array = Vec_new(0); + Vector *other = Vec_new(0); + String *stuff = SSTR_WRAP_C("stuff"); + + TEST_TRUE(runner, Vec_Equals(array, (Obj*)array), + "Array equal to self"); + + TEST_FALSE(runner, Vec_Equals(array, (Obj*)CFISH_TRUE), + "Array not equal to non-array"); + + TEST_TRUE(runner, Vec_Equals(array, (Obj*)other), + "Empty arrays are equal"); + + Vec_Push(array, (Obj*)CFISH_TRUE); + TEST_FALSE(runner, Vec_Equals(array, (Obj*)other), + "Add one elem and Equals returns false"); + + Vec_Push(other, (Obj*)CFISH_TRUE); + TEST_TRUE(runner, Vec_Equals(array, (Obj*)other), + "Add a matching elem and Equals returns true"); + + Vec_Store(array, 2, (Obj*)CFISH_TRUE); + TEST_FALSE(runner, Vec_Equals(array, (Obj*)other), + "Add elem after a NULL and Equals returns false"); + + Vec_Store(other, 2, (Obj*)CFISH_TRUE); + TEST_TRUE(runner, Vec_Equals(array, (Obj*)other), + "Empty elems don't spoil Equals"); + + Vec_Store(other, 2, INCREF(stuff)); + TEST_FALSE(runner, Vec_Equals(array, (Obj*)other), + "Non-matching value spoils Equals"); + + Vec_Store(other, 2, NULL); + TEST_FALSE(runner, Vec_Equals(array, (Obj*)other), + "NULL value spoils Equals"); + TEST_FALSE(runner, Vec_Equals(other, (Obj*)array), + "NULL value spoils Equals (reversed)"); + + Vec_Excise(array, 1, 2); // removes empty elems + DECREF(Vec_Delete(other, 1)); // leaves NULL in place of deleted elem + DECREF(Vec_Delete(other, 2)); + TEST_FALSE(runner, Vec_Equals(array, (Obj*)other), + "Empty trailing elements spoil Equals"); + + DECREF(array); + DECREF(other); +} + +static void +test_Store_Fetch(TestBatchRunner *runner) { + Vector *array = Vec_new(0); + String *elem; + + TEST_TRUE(runner, Vec_Fetch(array, 2) == NULL, "Fetch beyond end"); + + Vec_Store(array, 2, (Obj*)Str_newf("foo")); + elem = (String*)CERTIFY(Vec_Fetch(array, 2), STRING); + TEST_UINT_EQ(runner, 3, Vec_Get_Size(array), "Store updates size"); + TEST_TRUE(runner, Str_Equals_Utf8(elem, "foo", 3), "Store"); + + elem = (String*)INCREF(elem); + TEST_INT_EQ(runner, 2, CFISH_REFCOUNT_NN(elem), + "start with refcount of 2"); + Vec_Store(array, 2, (Obj*)Str_newf("bar")); + TEST_INT_EQ(runner, 1, CFISH_REFCOUNT_NN(elem), + "Displacing elem via Store updates refcount"); + DECREF(elem); + elem = (String*)CERTIFY(Vec_Fetch(array, 2), STRING); + TEST_TRUE(runner, Str_Equals_Utf8(elem, "bar", 3), "Store displacement"); + + DECREF(array); + + array = S_array_with_garbage(); + Vec_Store(array, 40, (Obj*)CFISH_TRUE); + bool all_null = true; + for (size_t i = 10; i < 40; i++) { + if (Vec_Fetch(array, i) != NULL) { all_null = false; } + } + TEST_TRUE(runner, all_null, "Out-of-bounds Store clears excised elements"); + DECREF(array); +} + +static void +test_Push_Pop_Insert(TestBatchRunner *runner) { + Vector *array = Vec_new(0); + String *elem; + + TEST_UINT_EQ(runner, Vec_Get_Size(array), 0, "size starts at 0"); + TEST_TRUE(runner, Vec_Pop(array) == NULL, + "Pop from empty array returns NULL"); + + Vec_Push(array, (Obj*)Str_newf("a")); + Vec_Push(array, (Obj*)Str_newf("b")); + Vec_Push(array, (Obj*)Str_newf("c")); + + TEST_UINT_EQ(runner, Vec_Get_Size(array), 3, "size after Push"); + TEST_TRUE(runner, NULL != CERTIFY(Vec_Fetch(array, 2), STRING), "Push"); + + elem = (String*)CERTIFY(Vec_Pop(array), STRING); + TEST_TRUE(runner, Str_Equals_Utf8(elem, "c", 1), "Pop"); + TEST_UINT_EQ(runner, Vec_Get_Size(array), 2, "size after Pop"); + DECREF(elem); + + Vec_Insert(array, 0, (Obj*)Str_newf("foo")); + elem = (String*)CERTIFY(Vec_Fetch(array, 0), STRING); + TEST_TRUE(runner, Str_Equals_Utf8(elem, "foo", 3), "Insert"); + TEST_UINT_EQ(runner, Vec_Get_Size(array), 3, "size after Insert"); + + for (int i = 0; i < 256; ++i) { + Vec_Push(array, (Obj*)Str_newf("flotsam")); + } + for (size_t i = 0; i < 512; ++i) { + Vec_Insert(array, i, (Obj*)Str_newf("jetsam")); + } + TEST_UINT_EQ(runner, Vec_Get_Size(array), 3 + 256 + 512, + "size after exercising Push and Insert"); + + DECREF(array); +} + +static void +test_Insert_All(TestBatchRunner *runner) { + int64_t i; + + { + Vector *dst = Vec_new(20); + Vector *src = Vec_new(10); + Vector *wanted = Vec_new(30); + + for (i = 0; i < 10; i++) { Vec_Push(dst, (Obj*)Int_new(i)); } + for (i = 0; i < 10; i++) { Vec_Push(dst, (Obj*)Int_new(i + 20)); } + for (i = 0; i < 10; i++) { Vec_Push(src, (Obj*)Int_new(i + 10)); } + for (i = 0; i < 30; i++) { Vec_Push(wanted, (Obj*)Int_new(i)); } + + Vec_Insert_All(dst, 10, src); + TEST_TRUE(runner, Vec_Equals(dst, (Obj*)wanted), "Insert_All between"); + + DECREF(wanted); + DECREF(src); + DECREF(dst); + } + + { + Vector *dst = Vec_new(10); + Vector *src = Vec_new(10); + Vector *wanted = Vec_new(30); + + for (i = 0; i < 10; i++) { Vec_Push(dst, (Obj*)Int_new(i)); } + for (i = 0; i < 10; i++) { Vec_Push(src, (Obj*)Int_new(i + 20)); } + for (i = 0; i < 10; i++) { Vec_Push(wanted, (Obj*)Int_new(i)); } + for (i = 0; i < 10; i++) { + Vec_Store(wanted, (size_t)i + 20, (Obj*)Int_new(i + 20)); + } + + Vec_Insert_All(dst, 20, src); + TEST_TRUE(runner, Vec_Equals(dst, (Obj*)wanted), "Insert_All after"); + + DECREF(wanted); + DECREF(src); + DECREF(dst); + } +} + +static void +test_Delete(TestBatchRunner *runner) { + Vector *wanted = Vec_new(5); + Vector *got = Vec_new(5); + uint32_t i; + + for (i = 0; i < 5; i++) { Vec_Push(got, (Obj*)Str_newf("%u32", i)); } + Vec_Store(wanted, 0, (Obj*)Str_newf("0", i)); + Vec_Store(wanted, 1, (Obj*)Str_newf("1", i)); + Vec_Store(wanted, 4, (Obj*)Str_newf("4", i)); + DECREF(Vec_Delete(got, 2)); + DECREF(Vec_Delete(got, 3)); + TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), "Delete"); + + TEST_TRUE(runner, Vec_Delete(got, 25000) == NULL, + "Delete beyond array size returns NULL"); + + DECREF(wanted); + DECREF(got); +} + +static void +test_Resize(TestBatchRunner *runner) { + Vector *array = Vec_new(3); + uint32_t i; + + for (i = 0; i < 2; i++) { Vec_Push(array, (Obj*)Str_newf("%u32", i)); } + TEST_UINT_EQ(runner, Vec_Get_Capacity(array), 3, "Start with capacity 3"); + + Vec_Resize(array, 4); + TEST_UINT_EQ(runner, Vec_Get_Size(array), 4, "Resize up"); + TEST_UINT_EQ(runner, Vec_Get_Capacity(array), 4, + "Resize changes capacity"); + + Vec_Resize(array, 2); + TEST_UINT_EQ(runner, Vec_Get_Size(array), 2, "Resize down"); + TEST_TRUE(runner, Vec_Fetch(array, 2) == NULL, "Resize down zaps elem"); + + Vec_Resize(array, 2); + TEST_UINT_EQ(runner, Vec_Get_Size(array), 2, "Resize to same size"); + + DECREF(array); + + array = S_array_with_garbage(); + Vec_Resize(array, 40); + bool all_null = true; + for (size_t i = 10; i < 40; i++) { + if (Vec_Fetch(array, i) != NULL) { all_null = false; } + } + TEST_TRUE(runner, all_null, "Resize clears excised elements"); + DECREF(array); +} + +static void +test_Excise(TestBatchRunner *runner) { + Vector *wanted = Vec_new(5); + Vector *got = Vec_new(5); + + for (uint32_t i = 0; i < 5; i++) { + Vec_Push(wanted, (Obj*)Str_newf("%u32", i)); + Vec_Push(got, (Obj*)Str_newf("%u32", i)); + } + + Vec_Excise(got, 7, 1); + TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), + "Excise outside of range is no-op"); + + Vec_Excise(got, 2, 2); + DECREF(Vec_Delete(wanted, 2)); + DECREF(Vec_Delete(wanted, 3)); + Vec_Store(wanted, 2, Vec_Delete(wanted, 4)); + Vec_Resize(wanted, 3); + TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), + "Excise multiple elems"); + + Vec_Excise(got, 2, 2); + Vec_Resize(wanted, 2); + TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), + "Splicing too many elems truncates"); + + Vec_Excise(got, 0, 1); + Vec_Store(wanted, 0, Vec_Delete(wanted, 1)); + Vec_Resize(wanted, 1); + TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), + "Excise first elem"); + + DECREF(got); + DECREF(wanted); +} + +static void +test_Push_All(TestBatchRunner *runner) { + Vector *wanted = Vec_new(0); + Vector *got = Vec_new(0); + Vector *scratch = Vec_new(0); + Vector *empty = Vec_new(0); + uint32_t i; + + for (i = 0; i < 40; i++) { Vec_Push(wanted, (Obj*)Str_newf("%u32", i)); } + Vec_Push(wanted, NULL); + for (i = 0; i < 20; i++) { Vec_Push(got, (Obj*)Str_newf("%u32", i)); } + for (i = 20; i < 40; i++) { Vec_Push(scratch, (Obj*)Str_newf("%u32", i)); } + Vec_Push(scratch, NULL); + + Vec_Push_All(got, scratch); + TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), "Push_All"); + + Vec_Push_All(got, empty); + TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), + "Push_All with empty array"); + + DECREF(wanted); + DECREF(got); + DECREF(scratch); + DECREF(empty); +} + +static void +test_Slice(TestBatchRunner *runner) { + Vector *array = Vec_new(0); + for (uint32_t i = 0; i < 10; i++) { Vec_Push(array, (Obj*)Str_newf("%u32", i)); } + { + Vector *slice = Vec_Slice(array, 0, 10); + TEST_TRUE(runner, Vec_Equals(array, (Obj*)slice), "Slice entire array"); + DECREF(slice); + } + { + Vector *slice = Vec_Slice(array, 0, 11); + TEST_TRUE(runner, Vec_Equals(array, (Obj*)slice), + "Exceed length"); + DECREF(slice); + } + { + Vector *wanted = Vec_new(0); + Vec_Push(wanted, (Obj*)Str_newf("9")); + Vector *slice = Vec_Slice(array, 9, 11); + TEST_TRUE(runner, Vec_Equals(slice, (Obj*)wanted), + "Exceed length, start near end"); + DECREF(slice); + DECREF(wanted); + } + { + Vector *slice = Vec_Slice(array, 0, 0); + TEST_TRUE(runner, Vec_Get_Size(slice) == 0, "empty slice"); + DECREF(slice); + } + { + Vector *slice = Vec_Slice(array, 20, 1); + TEST_TRUE(runner, Vec_Get_Size(slice) == 0, "exceed offset"); + DECREF(slice); + } + { + Vector *wanted = Vec_new(0); + Vec_Push(wanted, (Obj*)Str_newf("9")); + Vector *slice = Vec_Slice(array, 9, SIZE_MAX - 1); + TEST_TRUE(runner, Vec_Get_Size(slice) == 1, "guard against overflow"); + DECREF(slice); + DECREF(wanted); + } + DECREF(array); +} + +static void +test_Clone(TestBatchRunner *runner) { + Vector *array = Vec_new(0); + Vector *twin; + uint32_t i; + + for (i = 0; i < 10; i++) { + Vec_Push(array, (Obj*)Int_new(i)); + } + Vec_Push(array, NULL); + twin = Vec_Clone(array); + TEST_TRUE(runner, Vec_Equals(array, (Obj*)twin), "Clone"); + TEST_TRUE(runner, Vec_Fetch(array, 1) == Vec_Fetch(twin, 1), + "Clone doesn't clone elements"); + + DECREF(array); + DECREF(twin); +} + +static void +S_push(void *context) { + Vector *vec = (Vector*)context; + Vec_Push(vec, (Obj*)CFISH_TRUE); +} + +static void +S_insert_at_size_max(void *context) { + Vector *vec = (Vector*)context; + Vec_Insert(vec, SIZE_MAX, (Obj*)CFISH_TRUE); +} + +static void +S_store_at_size_max(void *context) { + Vector *vec = (Vector*)context; + Vec_Store(vec, SIZE_MAX, (Obj*)CFISH_TRUE); +} + +typedef struct { + Vector *vec; + Vector *other; +} VectorPair; + +static void +S_push_all(void *vcontext) { + VectorPair *context = (VectorPair*)vcontext; + Vec_Push_All(context->vec, context->other); +} + +static void +S_insert_all_at_size_max(void *vcontext) { + VectorPair *context = (VectorPair*)vcontext; + Vec_Insert_All(context->vec, SIZE_MAX, context->other); +} + +static void +S_test_exception(TestBatchRunner *runner, Err_Attempt_t func, void *context, + const char *test_name) { + Err *error = Err_trap(func, context); + TEST_TRUE(runner, error != NULL, test_name); + DECREF(error); +} + +static void +test_exceptions(TestBatchRunner *runner) { + { + Vector *vec = Vec_new(0); + vec->cap = MAX_VECTOR_SIZE; + vec->size = vec->cap; + S_test_exception(runner, S_push, vec, "Push throws on overflow"); + vec->size = 0; + DECREF(vec); + } + + { + Vector *vec = Vec_new(0); + S_test_exception(runner, S_insert_at_size_max, vec, + "Insert throws on overflow"); + DECREF(vec); + } + + { + Vector *vec = Vec_new(0); + S_test_exception(runner, S_store_at_size_max, vec, + "Store throws on overflow"); + DECREF(vec); + } + + { + VectorPair context; + context.vec = Vec_new(0); + context.vec->cap = 1000000000; + context.vec->size = context.vec->cap; + context.other = Vec_new(0); + context.other->cap = MAX_VECTOR_SIZE - context.vec->cap + 1; + context.other->size = context.other->cap; + S_test_exception(runner, S_push_all, &context, + "Push_All throws on overflow"); + context.vec->size = 0; + context.other->size = 0; + DECREF(context.other); + DECREF(context.vec); + } + + { + VectorPair context; + context.vec = Vec_new(0); + context.other = Vec_new(0); + S_test_exception(runner, S_insert_all_at_size_max, &context, + "Insert_All throws on overflow"); + DECREF(context.other); + DECREF(context.vec); + } +} + +static void +test_Sort(TestBatchRunner *runner) { + Vector *array = Vec_new(8); + Vector *wanted = Vec_new(8); + + Vec_Push(array, NULL); + Vec_Push(array, (Obj*)Str_newf("aaab")); + Vec_Push(array, (Obj*)Str_newf("ab")); + Vec_Push(array, NULL); + Vec_Push(array, NULL); + Vec_Push(array, (Obj*)Str_newf("aab")); + Vec_Push(array, (Obj*)Str_newf("b")); + + Vec_Push(wanted, (Obj*)Str_newf("aaab")); + Vec_Push(wanted, (Obj*)Str_newf("aab")); + Vec_Push(wanted, (Obj*)Str_newf("ab")); + Vec_Push(wanted, (Obj*)Str_newf("b")); + Vec_Push(wanted, NULL); + Vec_Push(wanted, NULL); + Vec_Push(wanted, NULL); + + Vec_Sort(array); + TEST_TRUE(runner, Vec_Equals(array, (Obj*)wanted), "Sort with NULLs"); + + DECREF(array); + DECREF(wanted); +} + +static void +test_Grow(TestBatchRunner *runner) { + Vector *array = Vec_new(500); + size_t cap; + + cap = Vec_Get_Capacity(array); + TEST_TRUE(runner, cap >= 500, "Array is created with minimum capacity"); + + Vec_Grow(array, 2000); + cap = Vec_Get_Capacity(array); + TEST_TRUE(runner, cap >= 2000, "Grow to larger capacity"); + + size_t old_cap = cap; + Vec_Grow(array, old_cap); + cap = Vec_Get_Capacity(array); + TEST_TRUE(runner, cap >= old_cap, "Grow to same capacity"); + + Vec_Grow(array, 1000); + cap = Vec_Get_Capacity(array); + TEST_TRUE(runner, cap >= 1000, "Grow to smaller capacity"); + + DECREF(array); +} + +void +TestVector_Run_IMP(TestVector *self, TestBatchRunner *runner) { + TestBatchRunner_Plan(runner, (TestBatch*)self, 62); + test_Equals(runner); + test_Store_Fetch(runner); + test_Push_Pop_Insert(runner); + test_Insert_All(runner); + test_Delete(runner); + test_Resize(runner); + test_Excise(runner); + test_Push_All(runner); + test_Slice(runner); + test_Clone(runner); + test_exceptions(runner); + test_Sort(runner); + test_Grow(runner); +} + + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestVector.cfh ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/TestVector.cfh b/runtime/test/Clownfish/Test/TestVector.cfh new file mode 100644 index 0000000..090b8b9 --- /dev/null +++ b/runtime/test/Clownfish/Test/TestVector.cfh @@ -0,0 +1,29 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +parcel TestClownfish; + +class Clownfish::Test::TestVector + inherits Clownfish::TestHarness::TestBatch { + + inert incremented TestVector* + new(); + + void + Run(TestVector *self, TestBatchRunner *runner); +} + + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/Util/TestAtomic.c ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/Util/TestAtomic.c b/runtime/test/Clownfish/Test/Util/TestAtomic.c new file mode 100644 index 0000000..f87279a --- /dev/null +++ b/runtime/test/Clownfish/Test/Util/TestAtomic.c @@ -0,0 +1,65 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define CFISH_USE_SHORT_NAMES +#define TESTCFISH_USE_SHORT_NAMES + +#include "Clownfish/Test/Util/TestAtomic.h" + +#include "Clownfish/Test.h" +#include "Clownfish/TestHarness/TestBatchRunner.h" +#include "Clownfish/Util/Atomic.h" +#include "Clownfish/Class.h" + +TestAtomic* +TestAtomic_new() { + return (TestAtomic*)Class_Make_Obj(TESTATOMIC); +} + +static void +test_cas_ptr(TestBatchRunner *runner) { + int foo = 1; + int bar = 2; + int *foo_pointer = &foo; + int *bar_pointer = &bar; + int *target = NULL; + + TEST_TRUE(runner, + Atomic_cas_ptr((void**)&target, NULL, foo_pointer), + "cas_ptr returns true on success"); + TEST_TRUE(runner, target == foo_pointer, "cas_ptr sets target"); + + target = NULL; + TEST_FALSE(runner, + Atomic_cas_ptr((void**)&target, bar_pointer, foo_pointer), + "cas_ptr returns false when it old_value doesn't match"); + TEST_TRUE(runner, target == NULL, + "cas_ptr doesn't do anything to target when old_value doesn't match"); + + target = foo_pointer; + TEST_TRUE(runner, + Atomic_cas_ptr((void**)&target, foo_pointer, bar_pointer), + "cas_ptr from one value to another"); + TEST_TRUE(runner, target == bar_pointer, "cas_ptr sets target"); +} + +void +TestAtomic_Run_IMP(TestAtomic *self, TestBatchRunner *runner) { + TestBatchRunner_Plan(runner, (TestBatch*)self, 6); + test_cas_ptr(runner); +} + + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/Util/TestAtomic.cfh ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/Util/TestAtomic.cfh b/runtime/test/Clownfish/Test/Util/TestAtomic.cfh new file mode 100644 index 0000000..2788342 --- /dev/null +++ b/runtime/test/Clownfish/Test/Util/TestAtomic.cfh @@ -0,0 +1,29 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +parcel TestClownfish; + +class Clownfish::Test::Util::TestAtomic + inherits Clownfish::TestHarness::TestBatch { + + inert incremented TestAtomic* + new(); + + void + Run(TestAtomic *self, TestBatchRunner *runner); +} + + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/Util/TestMemory.c ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/Util/TestMemory.c b/runtime/test/Clownfish/Test/Util/TestMemory.c new file mode 100644 index 0000000..8151c72 --- /dev/null +++ b/runtime/test/Clownfish/Test/Util/TestMemory.c @@ -0,0 +1,119 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define CFISH_USE_SHORT_NAMES +#define TESTCFISH_USE_SHORT_NAMES + +#include "charmony.h" + +#include "Clownfish/Test/Util/TestMemory.h" + +#include "Clownfish/Test.h" +#include "Clownfish/TestHarness/TestBatchRunner.h" +#include "Clownfish/Util/Memory.h" +#include "Clownfish/Class.h" + +TestMemory* +TestMemory_new() { + return (TestMemory*)Class_Make_Obj(TESTMEMORY); +} + +static void +test_oversize__growth_rate(TestBatchRunner *runner) { + bool success = true; + uint64_t size = 0; + double growth_count = 0; + double average_growth_rate = 0.0; + + while (size < SIZE_MAX) { + uint64_t next_size = Memory_oversize((size_t)size + 1, sizeof(void*)); + if (next_size < size) { + success = false; + FAIL(runner, "Asked for %" PRId64 ", got smaller amount %" PRId64, + size + 1, next_size); + break; + } + if (size > 0) { + growth_count += 1; + double growth_rate = CHY_U64_TO_DOUBLE(next_size) / + CHY_U64_TO_DOUBLE(size); + double sum = growth_rate + (growth_count - 1) * average_growth_rate; + average_growth_rate = sum / growth_count; + if (average_growth_rate < 1.1) { + FAIL(runner, "Average growth rate dropped below 1.1x: %f", + average_growth_rate); + success = false; + break; + } + } + size = next_size; + } + TEST_TRUE(runner, growth_count > 0, "Grew %f times", growth_count); + if (success) { + TEST_TRUE(runner, average_growth_rate > 1.1, + "Growth rate of oversize() averages above 1.1: %.3f", + average_growth_rate); + } + + for (size_t minimum = 1; minimum < 8; minimum++) { + uint64_t next_size = Memory_oversize(minimum, sizeof(void*)); + double growth_rate = CHY_U64_TO_DOUBLE(next_size) / (double)minimum; + TEST_TRUE(runner, growth_rate > 1.2, + "Growth rate is higher for smaller arrays (%u, %.3f)", + (unsigned)minimum, growth_rate); + } +} + +static void +test_oversize__ceiling(TestBatchRunner *runner) { + for (unsigned width = 0; width < 10; width++) { + size_t size = Memory_oversize(SIZE_MAX, width); + TEST_TRUE(runner, size == SIZE_MAX, + "Memory_oversize hits ceiling at SIZE_MAX (width %u)", width); + size = Memory_oversize(SIZE_MAX - 1, width); + TEST_TRUE(runner, size == SIZE_MAX, + "Memory_oversize hits ceiling at SIZE_MAX (width %u)", width); + } +} + +static void +test_oversize__rounding(TestBatchRunner *runner) { + unsigned widths[] = { 1, 2, 4, 0 }; + + for (int width_tick = 0; widths[width_tick] != 0; width_tick++) { + unsigned width = widths[width_tick]; + for (unsigned i = 0; i < 25; i++) { + size_t size = Memory_oversize(i, width); + size_t bytes = size * width; + if (bytes % sizeof(size_t) != 0) { + FAIL(runner, "Rounding failure for %u, width %u", + i, width); + return; + } + } + } + PASS(runner, "Round allocations up to the size of a pointer"); +} + +void +TestMemory_Run_IMP(TestMemory *self, TestBatchRunner *runner) { + TestBatchRunner_Plan(runner, (TestBatch*)self, 30); + test_oversize__growth_rate(runner); + test_oversize__ceiling(runner); + test_oversize__rounding(runner); +} + + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/Util/TestMemory.cfh ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/Util/TestMemory.cfh b/runtime/test/Clownfish/Test/Util/TestMemory.cfh new file mode 100644 index 0000000..d0b5803 --- /dev/null +++ b/runtime/test/Clownfish/Test/Util/TestMemory.cfh @@ -0,0 +1,29 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +parcel TestClownfish; + +class Clownfish::Test::Util::TestMemory + inherits Clownfish::TestHarness::TestBatch { + + inert incremented TestMemory* + new(); + + void + Run(TestMemory *self, TestBatchRunner *runner); +} + + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/Util/TestStringHelper.c ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/Util/TestStringHelper.c b/runtime/test/Clownfish/Test/Util/TestStringHelper.c new file mode 100644 index 0000000..2a873fd --- /dev/null +++ b/runtime/test/Clownfish/Test/Util/TestStringHelper.c @@ -0,0 +1,373 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> + +#define CFISH_USE_SHORT_NAMES +#define TESTCFISH_USE_SHORT_NAMES + +#include "Clownfish/Test/Util/TestStringHelper.h" + +#include "Clownfish/String.h" +#include "Clownfish/Err.h" +#include "Clownfish/Test.h" +#include "Clownfish/TestHarness/TestBatchRunner.h" +#include "Clownfish/Util/StringHelper.h" +#include "Clownfish/Class.h" + +/* This alternative implementation of utf8_valid() is (presumably) slower, but + * it implements the standard in a more linear, easy-to-grok way. + */ +#define TRAIL_OK(n) (n >= 0x80 && n <= 0xBF) +TestStringHelper* +TestStrHelp_new() { + return (TestStringHelper*)Class_Make_Obj(TESTSTRINGHELPER); +} + +static bool +S_utf8_valid_alt(const char *maybe_utf8, size_t size) { + const uint8_t *string = (const uint8_t*)maybe_utf8; + const uint8_t *const end = string + size; + while (string < end) { + int count = StrHelp_UTF8_COUNT[*string]; + bool valid = false; + if (count == 1) { + if (string[0] <= 0x7F) { + valid = true; + } + } + else if (count == 2) { + if (string[0] >= 0xC2 && string[0] <= 0xDF) { + if (TRAIL_OK(string[1])) { + valid = true; + } + } + } + else if (count == 3) { + if (string[0] == 0xE0) { + if (string[1] >= 0xA0 && string[1] <= 0xBF + && TRAIL_OK(string[2]) + ) { + valid = true; + } + } + else if (string[0] >= 0xE1 && string[0] <= 0xEC) { + if (TRAIL_OK(string[1]) + && TRAIL_OK(string[2]) + ) { + valid = true; + } + } + else if (string[0] == 0xED) { + if (string[1] >= 0x80 && string[1] <= 0x9F + && TRAIL_OK(string[2]) + ) { + valid = true; + } + } + else if (string[0] >= 0xEE && string[0] <= 0xEF) { + if (TRAIL_OK(string[1]) + && TRAIL_OK(string[2]) + ) { + valid = true; + } + } + } + else if (count == 4) { + if (string[0] == 0xF0) { + if (string[1] >= 0x90 && string[1] <= 0xBF + && TRAIL_OK(string[2]) + && TRAIL_OK(string[3]) + ) { + valid = true; + } + } + else if (string[0] >= 0xF1 && string[0] <= 0xF3) { + if (TRAIL_OK(string[1]) + && TRAIL_OK(string[2]) + && TRAIL_OK(string[3]) + ) { + valid = true; + } + } + else if (string[0] == 0xF4) { + if (string[1] >= 0x80 && string[1] <= 0x8F + && TRAIL_OK(string[2]) + && TRAIL_OK(string[3]) + ) { + valid = true; + } + } + } + + if (!valid) { + return false; + } + string += count; + } + + if (string != end) { + return false; + } + + return true; +} + +static void +test_overlap(TestBatchRunner *runner) { + size_t result; + result = StrHelp_overlap("", "", 0, 0); + TEST_UINT_EQ(runner, result, 0, "two empty strings"); + result = StrHelp_overlap("", "foo", 0, 3); + TEST_UINT_EQ(runner, result, 0, "first string is empty"); + result = StrHelp_overlap("foo", "", 3, 0); + TEST_UINT_EQ(runner, result, 0, "second string is empty"); + result = StrHelp_overlap("foo", "foo", 3, 3); + TEST_UINT_EQ(runner, result, 3, "equal strings"); + result = StrHelp_overlap("foo bar", "foo", 7, 3); + TEST_UINT_EQ(runner, result, 3, "first string is longer"); + result = StrHelp_overlap("foo", "foo bar", 3, 7); + TEST_UINT_EQ(runner, result, 3, "second string is longer"); + result = StrHelp_overlap("bar", "baz", 3, 3); + TEST_UINT_EQ(runner, result, 2, "different byte"); +} + + +static void +test_to_base36(TestBatchRunner *runner) { + char buffer[StrHelp_MAX_BASE36_BYTES]; + StrHelp_to_base36(UINT64_MAX, buffer); + TEST_STR_EQ(runner, "3w5e11264sgsf", buffer, "base36 UINT64_MAX"); + StrHelp_to_base36(1, buffer); + TEST_STR_EQ(runner, "1", buffer, "base36 1"); + TEST_INT_EQ(runner, buffer[1], 0, "base36 NULL termination"); +} + +static void +test_utf8_round_trip(TestBatchRunner *runner) { + int32_t code_point; + for (code_point = 0; code_point <= 0x10FFFF; code_point++) { + char buffer[4]; + uint32_t size = StrHelp_encode_utf8_char(code_point, buffer); + char *start = buffer; + char *end = start + size; + + // Verify length returned by encode_utf8_char(). + if (size != StrHelp_UTF8_COUNT[(unsigned char)buffer[0]]) { + break; + } + // Verify that utf8_valid() agrees with alternate implementation. + if (!!StrHelp_utf8_valid(start, size) + != !!S_utf8_valid_alt(start, size) + ) { + break; + } + + // Verify back_utf8_char(). + if (StrHelp_back_utf8_char(end, start) != start) { + break; + } + + // Verify round trip of encode/decode. + if (StrHelp_decode_utf8_char(buffer) != code_point) { + break; + } + } + if (code_point == 0x110000) { + PASS(runner, "Successfully round tripped 0 - 0x10FFFF"); + } + else { + FAIL(runner, "Failed round trip at 0x%.1X", (unsigned)code_point); + } +} + +static void +S_test_validity(TestBatchRunner *runner, const char *content, size_t size, + bool expected, const char *description) { + bool sane = StrHelp_utf8_valid(content, size); + bool double_check = S_utf8_valid_alt(content, size); + if (sane != double_check) { + FAIL(runner, "Disagreement: %s", description); + } + else { + TEST_TRUE(runner, sane == expected, "%s", description); + } +} + +static void +test_utf8_valid(TestBatchRunner *runner) { + // Musical symbol G clef: + // Code point: U+1D11E + // UTF-16: 0xD834 0xDD1E + // UTF-8 0xF0 0x9D 0x84 0x9E + S_test_validity(runner, "\xF0\x9D\x84\x9E", 4, true, + "Musical symbol G clef"); + S_test_validity(runner, "\xED\xA0\xB4\xED\xB4\x9E", 6, false, + "G clef as UTF-8 encoded UTF-16 surrogates"); + S_test_validity(runner, ".\xED\xA0\xB4.", 5, false, + "Isolated high surrogate"); + S_test_validity(runner, ".\xED\xB4\x9E.", 5, false, + "Isolated low surrogate"); + + // Shortest form. + S_test_validity(runner, ".\xC1\x9C.", 4, false, + "Non-shortest form ASCII backslash"); + S_test_validity(runner, ".\xC0\xAF.", 4, false, + "Non-shortest form ASCII slash"); + S_test_validity(runner, ".\xC0\x80.", 4, false, + "Non-shortest form ASCII NUL character"); + S_test_validity(runner, ".\xE0\x9F\xBF.", 5, false, + "Non-shortest form three byte sequence"); + S_test_validity(runner, ".\xF0\x8F\xBF\xBF.", 6, false, + "Non-shortest form four byte sequence"); + + // Range. + S_test_validity(runner, "\xF8\x88\x80\x80\x80", 5, false, "5-byte UTF-8"); + S_test_validity(runner, "\xF4\x8F\xBF\xBF", 4, true, + "Code point 0x10FFFF"); + S_test_validity(runner, "\xF4\x90\x80\x80", 4, false, + "Code point 0x110000 too large"); + S_test_validity(runner, "\xF5\x80\x80\x80", 4, false, + "Sequence starting with 0xF5"); + + // Truncated sequences. + S_test_validity(runner, "\xC2", 1, false, + "Truncated two byte sequence"); + S_test_validity(runner, "\xE2\x98", 2, false, + "Truncated three byte sequence"); + S_test_validity(runner, "\xF0\x9D\x84", 3, false, + "Truncated four byte sequence"); + + // Bad continuations. + S_test_validity(runner, "\xE2\x98\xBA\xE2\x98\xBA", 6, true, + "SmileySmiley"); + S_test_validity(runner, "\xE2\xBA\xE2\x98\xBA", 5, false, + "missing first continuation byte"); + S_test_validity(runner, "\xE2\x98\xE2\x98\xBA", 5, false, + "missing second continuation byte"); + S_test_validity(runner, "\xE2\xE2\x98\xBA", 4, false, + "missing both continuation bytes"); + S_test_validity(runner, "\xBA\xE2\x98\xBA\xE2\xBA", 5, false, + "missing first continuation byte (end)"); + S_test_validity(runner, "\xE2\x98\xBA\xE2\x98", 5, false, + "missing second continuation byte (end)"); + S_test_validity(runner, "\xE2\x98\xBA\xE2", 4, false, + "missing both continuation bytes (end)"); + S_test_validity(runner, "\xBA\xE2\x98\xBA", 4, false, + "isolated continuation byte 0xBA"); + S_test_validity(runner, "\x98\xE2\x98\xBA", 4, false, + "isolated continuation byte 0x98"); + S_test_validity(runner, "\xE2\x98\xBA\xBA", 4, false, + "isolated continuation byte 0xBA (end)"); + S_test_validity(runner, "\xE2\x98\xBA\x98", 4, false, + "isolated continuation byte 0x98 (end)"); + S_test_validity(runner, "\xF0xxxx", 5, false, + "missing continuation byte 2/4"); + S_test_validity(runner, "\xF0\x9Dxxxx", 5, false, + "missing continuation byte 3/4"); + S_test_validity(runner, "\xF0\x9D\x84xx", 5, false, + "missing continuation byte 4/4"); +} + +static void +S_validate_utf8(void *context) { + const char *text = (const char*)context; + StrHelp_validate_utf8(text, strlen(text), "src.c", 17, "fn"); +} + +static void +test_validate_utf8(TestBatchRunner *runner) { + { + Err *error = Err_trap(S_validate_utf8, "Sigma\xC1\x9C."); + TEST_TRUE(runner, error != NULL, "validate_utf8 throws"); + String *mess = Err_Get_Mess(error); + const char *expected = "Invalid UTF-8 after 'Sigma': C1 9C 2E\n"; + bool ok = Str_Starts_With_Utf8(mess, expected, strlen(expected)); + TEST_TRUE(runner, ok, "validate_utf8 throws correct error message"); + DECREF(error); + } + + { + Err *error = Err_trap(S_validate_utf8, + "xxx123456789\xE2\x93\xAA" + "1234567890\xC1\x9C."); + String *mess = Err_Get_Mess(error); + const char *expected = + "Invalid UTF-8 after '123456789\xE2\x93\xAA" + "1234567890': C1 9C 2E\n"; + bool ok = Str_Starts_With_Utf8(mess, expected, strlen(expected)); + TEST_TRUE(runner, ok, "validate_utf8 truncates long prefix"); + DECREF(error); + } +} + +static void +test_is_whitespace(TestBatchRunner *runner) { + TEST_TRUE(runner, StrHelp_is_whitespace(' '), "space is whitespace"); + TEST_TRUE(runner, StrHelp_is_whitespace('\n'), "newline is whitespace"); + TEST_TRUE(runner, StrHelp_is_whitespace('\t'), "tab is whitespace"); + TEST_TRUE(runner, StrHelp_is_whitespace('\v'), + "vertical tab is whitespace"); + TEST_FALSE(runner, StrHelp_is_whitespace('a'), "'a' isn't whitespace"); + TEST_FALSE(runner, StrHelp_is_whitespace(0), "NULL isn't whitespace"); + TEST_FALSE(runner, StrHelp_is_whitespace(0x263A), + "Smiley isn't whitespace"); +} + +static void +S_encode_utf8_char(void *context) { + int32_t *code_point_ptr = (int32_t*)context; + char buffer[4]; + StrHelp_encode_utf8_char(*code_point_ptr, buffer); +} + +static void +test_encode_utf8_char(TestBatchRunner *runner) { + int32_t code_point = 0x110000; + Err *error = Err_trap(S_encode_utf8_char, &code_point); + TEST_TRUE(runner, error != NULL, "Encode code point 0x110000 throws"); + DECREF(error); +} + +static void +test_back_utf8_char(TestBatchRunner *runner) { + char buffer[4]; + char *buf = buffer + 1; + uint32_t len = StrHelp_encode_utf8_char(0x263A, buffer); + char *end = buffer + len; + TEST_TRUE(runner, StrHelp_back_utf8_char(end, buffer) == buffer, + "back_utf8_char"); + TEST_TRUE(runner, StrHelp_back_utf8_char(end, buf) == NULL, + "back_utf8_char returns NULL rather than back up beyond start"); + TEST_TRUE(runner, StrHelp_back_utf8_char(buffer, buffer) == NULL, + "back_utf8_char returns NULL when end == start"); +} + +void +TestStrHelp_Run_IMP(TestStringHelper *self, TestBatchRunner *runner) { + TestBatchRunner_Plan(runner, (TestBatch*)self, 55); + test_overlap(runner); + test_to_base36(runner); + test_utf8_round_trip(runner); + test_utf8_valid(runner); + test_validate_utf8(runner); + test_is_whitespace(runner); + test_encode_utf8_char(runner); + test_back_utf8_char(runner); +} + + + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/Util/TestStringHelper.cfh ---------------------------------------------------------------------- diff --git a/runtime/test/Clownfish/Test/Util/TestStringHelper.cfh b/runtime/test/Clownfish/Test/Util/TestStringHelper.cfh new file mode 100644 index 0000000..752c553 --- /dev/null +++ b/runtime/test/Clownfish/Test/Util/TestStringHelper.cfh @@ -0,0 +1,29 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +parcel TestClownfish; + +class Clownfish::Test::Util::TestStringHelper nickname TestStrHelp + inherits Clownfish::TestHarness::TestBatch { + + inert incremented TestStringHelper* + new(); + + void + Run(TestStringHelper *self, TestBatchRunner *runner); +} + + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/TestClownfish.c ---------------------------------------------------------------------- diff --git a/runtime/test/TestClownfish.c b/runtime/test/TestClownfish.c new file mode 100644 index 0000000..e9ec7a3 --- /dev/null +++ b/runtime/test/TestClownfish.c @@ -0,0 +1,22 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "testcfish_parcel.h" + +void +testcfish_init_parcel() { +} + http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/TestClownfish.cfp ---------------------------------------------------------------------- diff --git a/runtime/test/TestClownfish.cfp b/runtime/test/TestClownfish.cfp new file mode 100644 index 0000000..6db4b4a --- /dev/null +++ b/runtime/test/TestClownfish.cfp @@ -0,0 +1,8 @@ +{ + "name": "TestClownfish", + "nickname": "TestCfish", + "version": "v0.5.0", + "prerequisites": { + "Clownfish": "v0.5.0" + } +}
