Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Enno Rehling 2018-06-03 11:03:31 +02:00
commit 9055d1aaaa
22 changed files with 387 additions and 165 deletions

2
clibs

@ -1 +1 @@
Subproject commit 147584ad70b220abf6a4e97ca76e785729b9ac32
Subproject commit f9842e07a442c5453c270badf25ab72633b4edf5

View file

@ -308,7 +308,7 @@ msgstr "\"$unit($mage) legt einen Schleier um die Ausrüstung von $unit.dative($
msgid "error5"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - Das Gebäude gehört uns nicht.\""
msgid "tactics_lost"
msgid "para_tactics_lost"
msgstr "\"$unit($unit) konnte dem Gegner eine Falle stellen.\""
msgid "error_lowstealth"
@ -1052,7 +1052,7 @@ msgstr "\"Diese Region wurde von den Göttern verflucht. Stinkende Nebel ziehen
msgid "recruit_archetype"
msgstr "\"$unit($unit) rekrutiert $int($amount) $localize($archetype).\""
msgid "tactics_won"
msgid "para_tactics_won"
msgstr "\"$unit($unit) überrascht den Gegner.\""
msgid "raindance_effect"

View file

@ -308,7 +308,7 @@ msgstr "\"$unit($mage) shrouds the equipment of $unit($target) in shadows.\""
msgid "error5"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - The building is not ours.\""
msgid "tactics_lost"
msgid "para_tactics_lost"
msgstr "\"$unit($unit) lured the enemy into an ambush.\""
msgid "error_lowstealth"
@ -1052,7 +1052,7 @@ msgstr "\"This region was cursed by the gods. Stinking vapors billow over the de
msgid "recruit_archetype"
msgstr "\"$unit($unit) recruits $int($amount) $localize($archetype).\""
msgid "tactics_won"
msgid "para_tactics_won"
msgstr "\"$unit($unit) surprises the enemies.\""
msgid "raindance_effect"

View file

@ -17,9 +17,7 @@ msgstr "Rostiges Kettenhemd"
msgctxt "spellinfo"
msgid "destroy_magic"
msgstr ""
"Dieser Zauber ermöglicht dem Magier, Verzauberungen "
"einer Einheit, eines Schiffes, Gebäudes oder auch "
"der Region aufzulösen."
"Dieser Zauber ermöglicht dem Magier, Verzauberungen einer Einheit, eines Schiffes, Gebäudes oder auch der Region aufzulösen."
msgctxt "spell"
msgid "shadowknights"
@ -168,9 +166,6 @@ msgstr "TRÄNKE"
msgid "northwest"
msgstr "Nordwesten"
msgid "spinx09"
msgstr "Das Schiff mit dem Stern im Wappen liegt neben dem der einen Mandelkern hat"
msgctxt "iteminfo"
msgid "cookie"
msgstr "Kleines trockenes Dauergebäck, m od. s; - u. -es, - u. -e"

View file

@ -486,3 +486,13 @@ function test_rising_undead()
assert_equal(2, u.number)
assert_equal(u.number, u:get_item('rustysword'))
end
function test_dwarf_mining()
local f = faction.create('dwarf')
local r = region.create(0, 0, 'plain')
local u = unit.create(f, r)
u.name = 'Xolgrim'
u:set_skill('mining', 2)
assert_equal(2, u:get_skill('mining'))
assert_equal(4, u:eff_skill('mining'))
end

View file

@ -2960,10 +2960,10 @@ static void print_stats(battle * b)
unit *u = tf->unit;
message *m = NULL;
if (!is_attacker(tf)) {
m = msg_message("tactics_lost", "unit", u);
m = msg_message("para_tactics_lost", "unit", u);
}
else {
m = msg_message("tactics_won", "unit", u);
m = msg_message("para_tactics_won", "unit", u);
}
message_all(b, m);
msg_release(m);

View file

@ -517,7 +517,7 @@ static void report_crtypes(FILE * F, const struct locale *lang)
assert(hash > 0);
fprintf(F, "MESSAGETYPE %d\n", hash);
fputc('\"', F);
fputs(str_escape(nrt_string(kmt->mtype, lang), buffer, sizeof(buffer)), F);
fputs(crescape(nrt_string(kmt->mtype, lang), buffer, sizeof(buffer)), F);
fputs("\";text\n", F);
fprintf(F, "\"%s\";section\n", kmt->mtype->section);
}

View file

@ -85,13 +85,15 @@ static void test_steal_okay(CuTest * tc) {
struct steal env;
race *rc;
struct terrain_type *ter;
message *msg;
test_setup();
ter = test_create_terrain("plain", LAND_REGION);
rc = test_create_race("human");
rc->flags = 0;
setup_steal(&env, ter, rc);
CuAssertPtrEquals(tc, 0, steal_message(env.u, 0));
CuAssertPtrEquals(tc, NULL, msg = steal_message(env.u, 0));
assert(!msg);
test_teardown();
}
@ -739,6 +741,7 @@ static void test_expand_production(CuTest *tc) {
CuAssertPtrEquals(tc, u, results[0]->unit);
CuAssertPtrEquals(tc, u, results[1]->unit);
CuAssertIntEquals(tc, 0, u->n);
free(results);
test_teardown();
}

View file

@ -5,6 +5,8 @@
#include "alchemy.h"
#include "modules/score.h"
#include "kernel/build.h"
#include "kernel/building.h"
#include "kernel/item.h"
@ -229,6 +231,9 @@ static void handle_item(parseinfo *pi, const XML_Char *el, const XML_Char **attr
handle_bad_input(pi, el, attr[i]);
}
}
if (itype->score == 0) {
itype->score = default_score(itype);
}
itype->flags = flags;
}

View file

@ -40,12 +40,15 @@ static void setup_give(struct give *env) {
env->r = test_create_region(0, 0, ter);
env->src = test_create_unit(env->f1, env->r);
env->dst = env->f2 ? test_create_unit(env->f2, env->r) : 0;
env->itype = it_get_or_create(rt_get_or_create("money"));
env->itype->flags |= ITF_HERB;
if (env->f1 && env->f2) {
if (env->f2) {
ally * al = ally_add(&env->f2->allies, env->f1);
al->status = HELP_GIVE;
env->dst = test_create_unit(env->f2, env->r);
}
else {
env->dst = NULL;
}
if (env->lang) {
locale_setstring(env->lang, env->itype->rtype->_name, "SILBER");
@ -139,10 +142,12 @@ static void test_give_unit_to_ocean(CuTest * tc) {
static void test_give_men(CuTest * tc) {
struct give env = { 0 };
message * msg;
test_setup_ex(tc);
env.f2 = env.f1 = test_create_faction(NULL);
setup_give(&env);
CuAssertPtrEquals(tc, 0, give_men(1, env.src, env.dst, NULL));
CuAssertPtrEquals(tc, NULL, msg = give_men(1, env.src, env.dst, NULL));
assert(!msg);
CuAssertIntEquals(tc, 2, env.dst->number);
CuAssertIntEquals(tc, 0, env.src->number);
test_teardown();
@ -222,10 +227,13 @@ static void test_give_men_in_ocean(CuTest * tc) {
static void test_give_men_too_many(CuTest * tc) {
struct give env = { 0 };
message * msg;
test_setup_ex(tc);
env.f2 = env.f1 = test_create_faction(NULL);
setup_give(&env);
CuAssertPtrEquals(tc, 0, give_men(2, env.src, env.dst, NULL));
CuAssertPtrEquals(tc, NULL, msg = give_men(2, env.src, env.dst, NULL));
assert(!msg);
CuAssertIntEquals(tc, 2, env.dst->number);
CuAssertIntEquals(tc, 0, env.src->number);
test_teardown();

View file

@ -7,6 +7,8 @@
#include <CuTest.h>
#include <tests.h>
#include <assert.h>
void test_missing_message(CuTest *tc) {
message *msg;
@ -78,6 +80,7 @@ static void test_merge_split(CuTest *tc) {
static void test_noerror(CuTest *tc) {
unit *u;
struct locale *lang;
message *msg;
test_setup();
lang = test_create_locale();
@ -85,8 +88,10 @@ static void test_noerror(CuTest *tc) {
u->thisorder = parse_order("!@move", lang);
CuAssertIntEquals(tc, K_MOVE | CMD_QUIET | CMD_PERSIST, u->thisorder->command);
CuAssertTrue(tc, !is_persistent(u->thisorder));
CuAssertPtrEquals(tc, NULL, msg_error(u, u->thisorder, 100));
CuAssertPtrEquals(tc, NULL, msg_feedback(u, u->thisorder, "error_unit_not_found", NULL));
CuAssertPtrEquals(tc, NULL, msg = msg_error(u, u->thisorder, 100));
assert(!msg);
CuAssertPtrEquals(tc, NULL, msg = msg_feedback(u, u->thisorder, "error_unit_not_found", NULL));
assert(!msg);
test_teardown();
}

View file

@ -110,6 +110,15 @@ char* get_command(const order *ord, const struct locale *lang, char *sbuffer, si
return sbuffer;
}
const char *crescape(const char *str, char *buffer, size_t size) {
const char *replace = "\"\\";
const char * pos = strpbrk(str, replace);
if (!pos) {
return str;
}
return str_escape_ex(str, buffer, size, replace);
}
int stream_order(struct stream *out, const struct order *ord, const struct locale *lang, bool escape)
{
const char *text;
@ -151,7 +160,7 @@ int stream_order(struct stream *out, const struct order *ord, const struct local
char obuf[1024];
swrite(" ", 1, 1, out);
if (escape) {
text = str_escape(text, obuf, sizeof(obuf));
text = crescape(text, obuf, sizeof(obuf));
}
swrite(text, 1, strlen(text), out);
}

View file

@ -67,6 +67,7 @@ extern "C" {
bool is_exclusive(const order * ord);
bool is_repeated(keyword_t kwd);
bool is_long(keyword_t kwd);
const char *crescape(const char *str, char *buffer, size_t size);
char *write_order(const order * ord, const struct locale *lang,
char *buffer, size_t size);

View file

@ -14,6 +14,7 @@
#include <tests.h>
#include <CuTest.h>
#include <stdlib.h>
#include <string.h>
static void test_create_order(CuTest *tc) {
char cmd[32];
@ -239,6 +240,7 @@ static void test_is_persistent(CuTest *tc) {
ord = parse_order("@invalid", lang);
CuAssertPtrEquals(tc, NULL, ord);
free_order(ord);
ord = parse_order("give", lang);
CuAssertIntEquals(tc, K_GIVE, ord->command);
@ -290,6 +292,7 @@ static void test_is_silent(CuTest *tc) {
ord = parse_order("@invalid", lang);
CuAssertPtrEquals(tc, NULL, ord);
free_order(ord);
ord = parse_order("// comment", lang);
CuAssertTrue(tc, is_persistent(ord));
@ -482,10 +485,60 @@ static void test_study_order_unknown_quoted(CuTest *tc) {
test_teardown();
}
static void test_create_order_long(CuTest *tc) {
char buffer[2048];
order *ord;
struct locale *lang;
stream out;
const char * longstr = "// BESCHREIBEN EINHEIT \"In weiÃ&#131; &#131; &#131; &#131; &#131; &#131; &#131; &#"
"131;&#131;&#131;&#131;&#131;&#131;&#131;?e GewÃ&#131;&#131;&#131;&#131;&#13"
"1;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;Ã&#131;&#131;&#131;"
"&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;Ã&#131;&#131;&#131;&#"
"131;&#131;&#131;&#131;&#131;&#131;&#131;&#130;Ã&#131;&#131;&#131;&#131;&#13"
"1;&#131;&#131;&#131;&#131;&#130;Ã&#131;&#131;&#131;&#131;&#131;&#131;&#131;"
"&#131;&#130;Ã&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#130;Ã&#131;&#131;&"
"#131;&#131;&#131;&#131;&#130;Ã&#131;&#131;&#131;&#131;&#131;&#130;Ã&#131;&#"
"131;&#131;&#131;&#130;Ã&#131;&#131;&#131;&#130;Ã&#131;&#131;&#130;Ã&#131;&#"
"130;Ã&#130;¢&#130;Ã&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&"
"#131;&#131;&#130;Ã&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#1"
"31;&#130;Ã&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#130;Ã&#13"
"1;&#131;&#131;&#131;&#131;&#131;&#131;&#131;&#130;Ã&#131;&#131;&#131;&#131;"
"&#131;&#131;&#131;&#130[...]hB&#65533;&#65533;2&#65533;xa&#65533;Hv$P&#65533;xa&#65533;&#65533;A&#65533;&#65533;&#65533;A&#65533;&#65533;";
test_setup();
lang = test_create_locale();
ord = parse_order(longstr, lang);
CuAssertIntEquals(tc, 0, ord->command);
mstream_init(&out);
stream_order(&out, ord, lang, true);
out.api->rewind(out.handle);
out.api->readln(out.handle, buffer, sizeof(buffer));
mstream_done(&out);
free_order(ord);
test_teardown();
}
static void test_crescape(CuTest *tc) {
char buffer[16];
const char *input = "12345678901234567890";
CuAssertStrEquals(tc, "1234", crescape("1234", buffer, 16));
CuAssertPtrEquals(tc, (void *)input, (void *)crescape(input, buffer, 16));
CuAssertStrEquals(tc, "\\\"1234\\\"", crescape("\"1234\"", buffer, 16));
CuAssertStrEquals(tc, "\\\"1234\\\"", buffer);
CuAssertStrEquals(tc, "\\\"1234", crescape("\"1234\"", buffer, 8));
/* unlike in C strings, only " and \ are escaped: */
CuAssertStrEquals(tc, "\\\"\\\\\n\r\'", crescape("\"\\\n\r\'", buffer, 16));
}
CuSuite *get_order_suite(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_crescape);
SUITE_ADD_TEST(suite, test_create_order);
SUITE_ADD_TEST(suite, test_create_order_long);
SUITE_ADD_TEST(suite, test_study_orders);
SUITE_ADD_TEST(suite, test_study_order);
SUITE_ADD_TEST(suite, test_study_order_unknown);

View file

@ -7,7 +7,7 @@
#ifndef ERESSEA_VERSION
/* the version number, if it was not passed to make with -D */
#define ERESSEA_VERSION "3.16.0"
#define ERESSEA_VERSION "3.17.0"
#endif
const char *eressea_version(void) {

View file

@ -46,7 +46,7 @@
static int read_permissions(variant *var, void *owner, struct gamedata *data)
{
attrib *a;
attrib *a = NULL;
UNUSED_ARG(var);
read_attribs(data, &a, owner);
a_remove(&a, a);

View file

@ -172,8 +172,8 @@ static order *monster_attack(unit * u, const unit * target)
if (monster_is_waiting(u))
return NULL;
if (u->region->land) {
assert(u->region->flags & RF_GUARDED);
if (u->region->land && (u->region->flags & RF_GUARDED) == 0) {
return NULL;
}
return create_order(K_ATTACK, u->faction->locale, "%i", target->no);
}

View file

@ -723,8 +723,9 @@ rp_messages(struct stream *out, message_list * msgs, faction * viewer, int inden
int k = 0;
struct mlist *m = msgs->begin;
while (m) {
/* messagetype * mt = m->type; */
if (!categorized || strcmp(m->msg->type->section, section) == 0) {
/* categorized messages need a section: */
assert(!categorized || (m->msg->type->section != NULL));
if (!categorized || m->msg->type->section == section) {
char lbuf[8192];
if (!k && categorized) {
@ -1037,6 +1038,9 @@ void report_region(struct stream *out, const region * r, faction * f)
if (wrptr(&bufp, &size, bytes) != 0)
WARN_STATIC_BUFFER();
if (is_mourning(r, turn + 1)) {
bytes = (int)str_strlcpy(bufp, " ", size);
if (wrptr(&bufp, &size, bytes) != 0)
WARN_STATIC_BUFFER();
bytes = (int)str_strlcpy(bufp, LOC(f->locale, "nr_mourning"), size);
if (wrptr(&bufp, &size, bytes) != 0)
WARN_STATIC_BUFFER();
@ -1877,6 +1881,9 @@ nr_building(struct stream *out, const region *r, const building *b, const factio
}
if (!building_finished(b)) {
bytes = (int)str_strlcpy(bufp, " ", size);
if (wrptr(&bufp, &size, bytes) != 0)
WARN_STATIC_BUFFER();
bytes = (int)str_strlcpy(bufp, LOC(lang, "nr_building_inprogress"), size);
if (wrptr(&bufp, &size, bytes) != 0)
WARN_STATIC_BUFFER();
@ -2306,11 +2313,11 @@ report_plaintext(const char *filename, report_context * ctx,
message_list *mlist = r_getmessages(r, f);
if (mlist) {
struct mlist **split = merge_messages(mlist, r->msgs);
rp_messages(out, mlist, f, 0, true);
rp_messages(out, mlist, f, 0, false);
split_messages(mlist, split);
}
else {
rp_messages(out, r->msgs, f, 0, true);
rp_messages(out, r->msgs, f, 0, false);
}
}

View file

@ -20,9 +20,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <platform.h>
#endif
#include "strings.h"
#include "assert.h"
/* libc includes */
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
@ -111,7 +111,8 @@ size_t str_slprintf(char * dst, size_t size, const char * format, ...)
return (size_t)result;
}
void str_replace(char *buffer, size_t size, const char *tmpl, const char *var, const char *value)
void str_replace(char *buffer, size_t size, const char *tmpl, const char *var,
const char *value)
{
size_t val_len = strlen(value);
size_t var_len = strlen(var);
@ -154,28 +155,28 @@ int str_hash(const char *s)
return key & 0x7FFFFFFF;
}
const char *str_escape(const char *str, char *buffer,
size_t len)
const char *str_escape_wrong(const char *str, char *buffer, size_t len)
{
const char *handle_start = strchr(str, '\"');
if (!handle_start) handle_start = strchr(str, '\\');
assert(buffer);
if (handle_start) {
const char *p;
char *o;
const char *p = str;
char *o = buffer;
size_t skip = handle_start - str;
if (skip > len) {
skip = len;
}
if (skip > 0) {
memcpy(buffer, str, skip);
o = buffer + skip;
p = str + skip;
o += skip;
p += skip;
len -= skip;
}
do {
if (*p == '\"' || *p == '\\') {
if (len < 2) {
*o = '\0';
break;
}
(*o++) = '\\';
@ -183,13 +184,15 @@ const char *str_escape(const char *str, char *buffer,
}
else {
if (len < 1) {
*o = '\0';
break;
}
--len;
}
if (len > 0) {
(*o++) = (*p);
} while (*p++);
}
} while (len > 0 && *p++);
*o = '\0';
return buffer;
}
return str;
@ -306,3 +309,100 @@ char *str_unescape(char *str) {
}
return str;
}
const char *str_escape_ex(const char *str, char *buffer, size_t size, const char *chars)
{
size_t slen = strlen(str);
const char *read = str;
char *write = buffer;
if (size < 1) {
return NULL;
}
while (slen > 0 && size > 1 && *read) {
const char *pos = strpbrk(read, chars);
size_t len = size;
if (pos) {
len = pos - read;
}
if (len < size) {
unsigned char ch = *(const unsigned char *)pos;
if (len > 0) {
if (len > slen) {
len = slen;
}
memmove(write, read, len);
slen -= len;
write += len;
read += len;
size -= len;
}
switch (ch) {
case '\t':
if (size > 2) {
*write++ = '\\';
*write++ = 't';
size -= 2;
}
else size = 1;
break;
case '\n':
if (size > 2) {
*write++ = '\\';
*write++ = 'n';
size -= 2;
}
else size = 1;
break;
case '\r':
if (size > 2) {
*write++ = '\\';
*write++ = 'r';
size -= 2;
}
else size = 1;
break;
case '\"':
case '\'':
case '\\':
if (size > 2) {
*write++ = '\\';
*write++ = ch;
size -= 2;
}
break;
default:
if (size > 5) {
int n = sprintf(write, "\\%03o", ch);
if (n > 0) {
assert(n == 5);
write += n;
size -= n;
}
else size = 1;
}
else size = 1;
}
++read;
--slen;
} else {
/* end of buffer space */
len = size - 1;
if (len > 0) {
if (len > slen) {
len = slen;
}
memmove(write, read, len);
write += len;
size -= len;
slen -= len;
break;
}
}
}
*write = '\0';
return buffer;
}
const char *str_escape(const char *str, char *buffer, size_t size) {
return str_escape_ex(str, buffer, size, "\n\t\r\'\"\\");
}

View file

@ -32,7 +32,8 @@ extern "C" {
size_t str_strlcat(char *dst, const char *src, size_t len);
char *str_strdup(const char *s);
const char *str_escape(const char *str, char *buffer, size_t len);
const char *str_escape(const char *str, char *buffer, size_t size);
const char *str_escape_ex(const char *str, char *buffer, size_t size, const char *chars);
char *str_unescape(char *str);
unsigned int jenkins_hash(unsigned int a);

View file

@ -22,12 +22,36 @@ static void test_str_unescape(CuTest * tc)
CuAssertStrEquals(tc, "\"\\\n\t\ra", scratch);
}
static void test_str_escape_ex(CuTest * tc)
{
char scratch[16];
CuAssertPtrEquals(tc, NULL, (void *)str_escape_ex("1234", scratch, 0, "\\\""));
memset(scratch, 0, sizeof(scratch));
CuAssertStrEquals(tc, "1234", (void *)str_escape_ex("1234\000abcd", scratch, 16, "\\\""));
CuAssertIntEquals(tc, 0, scratch[5]);
}
static void test_str_escape(CuTest * tc)
{
char scratch[64];
CuAssertStrEquals(tc, "12345678901234567890", str_escape("12345678901234567890", scratch, 16));
CuAssertStrEquals(tc, "123456789\\\"12345", str_escape("123456789\"1234567890", scratch, 16));
CuAssertStrEquals(tc, "1234567890123456", str_escape("1234567890123456\"890", scratch, 16));
char scratch[16];
CuAssertPtrEquals(tc, NULL, (void *)str_escape("1234", scratch, 0));
CuAssertStrEquals(tc, "", str_escape("1234", scratch, 1));
CuAssertStrEquals(tc, "1", str_escape("1234", scratch, 2));
CuAssertStrEquals(tc, "\\n", str_escape("\n234", scratch, 3));
CuAssertStrEquals(tc, "\\n\\r\\t\\\\\\\"\\\'", str_escape("\n\r\t\\\"\'", scratch, 16));
CuAssertStrEquals(tc, "12345678", str_escape("12345678", scratch, 16));
CuAssertStrEquals(tc, "123456789012345", str_escape("123456789012345", scratch, 16));
CuAssertStrEquals(tc, "12345678901234", str_escape("12345678901234\n", scratch, 15));
CuAssertStrEquals(tc, "123456789012345", str_escape("12345678901234567890", scratch, 16));
CuAssertStrEquals(tc, "\\\\\\\"234", str_escape("\\\"234567890", scratch, 8));
CuAssertStrEquals(tc, "\\\"\\\\234", str_escape("\"\\234567890", scratch, 8));
CuAssertStrEquals(tc, "123456789012345", str_escape("12345678901234567890", scratch, 16));
CuAssertStrEquals(tc, "123456789\\\"1234", str_escape("123456789\"1234567890", scratch, 16));
CuAssertStrEquals(tc, "123456789012345", str_escape("1234567890123456\"890", scratch, 16));
CuAssertStrEquals(tc, "hello world", str_escape("hello world", scratch, sizeof(scratch)));
CuAssertStrEquals(tc, "hello \\\"world\\\"", str_escape("hello \"world\"", scratch, sizeof(scratch)));
CuAssertStrEquals(tc, "\\\"\\\\", str_escape("\"\\", scratch, sizeof(scratch)));
@ -135,6 +159,7 @@ CuSuite *get_strings_suite(void)
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_str_hash);
SUITE_ADD_TEST(suite, test_str_escape);
SUITE_ADD_TEST(suite, test_str_escape_ex);
SUITE_ADD_TEST(suite, test_str_unescape);
SUITE_ADD_TEST(suite, test_str_replace);
SUITE_ADD_TEST(suite, test_str_slprintf);