server-1.12/test/bugs/bugtrack/check_1727944.c

452 lines
17 KiB
C

/*
* CrossFire, A Multiplayer game for X-windows
*
* Copyright (C) 2007 Crossfire Development Team
* Copyright (C) 1992 Frank Tore Johansen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* The authors can be reached via e-mail at crossfire-devel@real-time.com
*/
/**
* @file
* This is the unit tests file for the bug #1727944: The horn of plenty
* location: http://sourceforge.net/tracker/index.php?func=detail&aid=1727944&group_id=13833&atid=113833.
*
* I did try different combos to generate an empty horn, so far no success...
* @author Nicolas Weeger
* @date 2007-06-04
*/
#include <stdlib.h>
#include <check.h>
#include <global.h>
void setup(void) {
/* put any initialisation steps here, they will be run before each testcase */
}
void teardown(void) {
/* put any cleanup steps here, they will be run after each testcase */
}
#if 0
static mapstruct *get_random_map(mapstruct *map) {
object *exit_ob;
mapstruct *random;
RMParms rp;
char newmap_name[HUGE_BUF], *cp;
static int reference_number = 0;
int x, y;
exit_ob = NULL;
for (x = 0; x < MAP_WIDTH(map) && exit_ob == NULL; x++) {
for (y = 0; y < MAP_HEIGHT(map) && exit_ob == NULL; y++) {
for (exit_ob = GET_MAP_OB(map, x, y); exit_ob != NULL; exit_ob = exit_ob->above)
if (exit_ob->type == EXIT && exit_ob->msg != NULL)
break;
}
}
if (!exit_ob)
/* this means we reached the end of the random part. */
return NULL;
/* copied from server/server.c:enter_random_map(). */
memset(&rp, 0, sizeof(RMParms));
rp.Xsize = -1;
rp.Ysize = -1;
rp.region = get_region_by_map(exit_ob->map);
if (exit_ob->msg)
set_random_map_variable(&rp, exit_ob->msg);
rp.origin_x = exit_ob->x;
rp.origin_y = exit_ob->y;
strcpy(rp.origin_map, map->path);
/* If we have a final_map, use it as a base name to give some clue
* as where the player is. Otherwise, use the origin map.
* Take the last component (after the last slash) to give
* shorter names without bogus slashes.
*/
if (rp.final_map[0]) {
cp = strrchr(rp.final_map, '/');
if (!cp)
cp = rp.final_map;
} else {
char buf[HUGE_BUF];
cp = strrchr(rp.origin_map, '/');
if (!cp)
cp = rp.origin_map;
/* Need to strip of any trailing digits, if it has them */
snprintf(buf, sizeof(buf), "%s", cp);
while (isdigit(buf[strlen(buf)-1]))
buf[strlen(buf)-1] = 0;
cp = buf;
}
snprintf(newmap_name, sizeof(newmap_name), "/random/%s%04d", cp+1, reference_number++);
/* now to generate the actual map. */
return generate_random_map(newmap_name, &rp, NULL);
}
static void do_run() {
mapstruct *worldmap;
mapstruct *random;
mapstruct *old;
int iteration, x, y, map;
object *check;
char path[150];
for (map = 1; map <= 3; map++) {
snprintf(path, sizeof(path), "/whalingoutpost/underwaterdungeon/level%d", map);
worldmap = ready_map_name(path, 0);
fail_unless(worldmap != NULL, "Can't load %s", path);
random = worldmap;
old = NULL;
iteration = 0;
while (random != NULL) {
random = get_random_map(random);
if (!random)
break;
if (old)
delete_map(old);
old = random;
iteration++;
for (x = 0; x < MAP_WIDTH(random); x++) {
for (y = 0; y < MAP_HEIGHT(random); y++) {
for (check = GET_MAP_OB(random, x, y); check; check = check->above) {
if (check->type == HORN && check->title && strcmp(check->title, "of Plenty") == 0)
fail_unless(check->inv != NULL, "Horn has empty inventory!");
}
}
}
}
fail_unless(iteration != 0, "did %d iterations", iteration);
if (old)
delete_map(old);
}
}
#endif
#if 0
static void do_run() {
mapstruct *map, *overlay;
int x, y, found = 0, test = 0;
object *check;
overlay = ready_map_name("../../rsc/bug_1727944_unique", MAP_PLAYER_UNIQUE);
fail_unless(overlay != NULL, "Couldn't load unique map ../../rsc/bug_1727944_unique");
while (found == 0 && test < 10) {
map = ready_map_name("../../rsc/bug_1727944", MAP_PLAYER_UNIQUE);
fail_unless(map != NULL, "couldn't load map ../../rsc/bug_1727944");
for (x = 0; x < MAP_WIDTH(map); x++) {
for (y = 0; y < MAP_HEIGHT(map); y++) {
for (check = GET_MAP_OB(map, x, y); check; check = check->above) {
if (check->type == HORN) {
fail_unless(check->inv != NULL, "Horn has empty inventory!");
fail_unless(check->inv->below == NULL, "Horn has 2 items in inventory!");
if (check->title && strcmp(check->title, "of Plenty") == 0) {
remove_ob(check);
insert_ob_in_map_at(check, overlay, NULL, 0, 2, 3);
found++;
break;
}
}
}
}
}
delete_map(map);
test++;
}
save_map(overlay, SAVE_MODE_OVERLAY);
delete_map(overlay);
}
#endif
extern int artifact_init;
extern int arch_init;
/* Copied from loader.l */
extern const char *const spell_mapping[];
static void local_check_loaded_object(object *op) {
int ip;
if (artifact_init)
/* Artifacts are special beasts, let's not check them. */
return;
/* We do some specialized handling to handle legacy cases of name_pl.
* If the object doesn't have a name_pl, we just use the object name -
* this isn't perfect (things won't be properly pluralized), but works to
* that degree (5 heart is still quite understandable). But the case we
* also have to catch is if this object is not using the normal name for
* the object. In that case, we also want to use the loaded name.
* Otherwise, what happens is that the the plural name will lose
* information (appear as just 'hearts' and not 'goblins heart')
*/
if (op->arch && op->name != op->arch->clone.name && op->name_pl == op->arch->clone.name_pl) {
if (op->name_pl)
free_string(op->name_pl);
op->name_pl = NULL;
}
if (!op->name_pl && op->name)
op->name_pl = add_string(op->name);
/* objects now have a materialname. try to patch it in */
if (!(IS_WEAPON(op) && op->level > 0)) {
if (op->map != NULL)
set_materialname(op, op->map->difficulty, NULL);
else
set_materialname(op, 5, NULL);
}
/* only do these when program is first run - a bit
* excessive to do this at every run - most of this is
* really just to catch any errors - program will still run, but
* not in the ideal fashion.
*/
if ((op->type == WEAPON || op->type == BOW) && arch_init) {
if (!op->skill) {
LOG(llevError, "Weapon %s lacks a skill.\n", op->name);
} else if ((!strcmp(op->skill, "one handed weapons") && op->body_info[1] != -1)
|| (!strcmp(op->skill, "two handed weapons") && op->body_info[1] != -2)) {
LOG(llevError, "weapon %s arm usage does not match skill: %d, %s\n",
op->name, op->body_info[1], op->skill);
}
}
/* We changed last_heal to gen_sp_armour, which is what it
* really does for many objects. Need to catch any in maps
* that may have an old value.
*/
if ((op->type == WEAPON)
|| (op->type == ARMOUR)
|| (op->type == HELMET)
|| (op->type == SHIELD)
|| (op->type == RING)
|| (op->type == BOOTS)
|| (op->type == GLOVES)
|| (op->type == AMULET)
|| (op->type == GIRDLE)
|| (op->type == BRACERS)
|| (op->type == CLOAK)) {
if (op->last_heal) {
LOG(llevDebug, "Object %s still has last_heal set, not gen_sp_armour\n", op->name ? op->name : "NULL");
op->gen_sp_armour = op->last_heal;
op->last_heal = 0;
}
ip = calc_item_power(op, 0);
/* Legacy objects from before item power was in the game */
if (!op->item_power && ip) {
if (ip > 3) {
LOG(llevDebug, "Object %s had no item power, using %d\n", op->name ? op->name : "NULL", ip);
}
op->item_power = ip;
}
/* Check for possibly bogus values. Has to meet both these criteria -
* something that has item_power 1 is probably just fine if our calculated
* value is 1 or 2 - these values are small enough that hard to be precise.
* similarly, it item_power is 0, the first check will always pass,
* but not the second one.
*/
if (ip > 2*op->item_power && ip > (op->item_power+3)) {
LOG(llevDebug, "Object %s seems to have too low item power? %d > %d\n", op->name ? op->name : "NULL", ip, op->item_power);
}
}
/* Old spellcasting object - need to load in the appropiate object */
if ((op->type == ROD || op->type == WAND || op->type == SCROLL || op->type == HORN || op->type == FIREWALL || /* POTIONS and ALTARS don't always cast spells, but if they do, update them */ ((op->type == POTION || op->type == ALTAR) && op->stats.sp))
&& !op->inv
&& !arch_init) {
object *tmp;
/* Fireall is bizarre in that spell type was stored in dam. Rest are 'normal'
* in that spell was stored in sp.
*/
tmp = create_archetype(spell_mapping[op->type == FIREWALL ? op->stats.dam : op->stats.sp]);
insert_ob_in_ob(tmp, op);
op->randomitems = NULL; /* So another spell isn't created for this object */
}
/* spellbooks & runes use slaying. But not to arch name, but to spell name */
if ((op->type == SPELLBOOK || op->type == RUNE) && op->slaying && !op->inv && !arch_init) {
object *tmp;
tmp = create_archetype_by_object_name(op->slaying);
insert_ob_in_ob(tmp, op);
op->randomitems = NULL; /* So another spell isn't created for this object */
/* without this, value is all screwed up */
op->value = op->arch->clone.value*op->inv->value;
}
if (QUERY_FLAG(op, FLAG_MONSTER)) {
if (op->stats.hp > op->stats.maxhp)
LOG(llevDebug, "Monster %s has hp set higher than maxhp (%d>%d)\n", op->name, op->stats.hp, op->stats.maxhp);
}
if ((QUERY_FLAG(op, FLAG_GENERATOR) && QUERY_FLAG(op, FLAG_CONTENT_ON_GEN))
|| op->type == CREATOR
|| op->type == CONVERTER) {
/* Object will duplicate it's content as part of the
* generation process. To do this, we must flag inventory
* so it remains unevaluated concerning the randomitems and
* the living (a demonlord shouldn't cast from inside generator!)
*/
flag_inv(op, FLAG_IS_A_TEMPLATE);
}
/* Here we'll handle custom monsters. In order to handle them correctly, especially in the fix_object
* method, we'll create a new temporary archetype containing defined values.
* Of course this doesn't apply when loading archetypes or artifacts.
*/
if (arch_init == 0 && artifact_init == 0 && QUERY_FLAG(op, FLAG_MONSTER) && op->arch && !can_merge(op, &op->arch->clone)) {
archetype *temp = get_archetype_struct();
temp->reference_count++;
temp->name = add_string(op->arch->name);
temp->tail_x = op->arch->tail_x;
temp->tail_y = op->arch->tail_y;
copy_object(op, &temp->clone);
temp->clone.inv = NULL;
temp->clone.env = NULL;
temp->clone.x = 0;
temp->clone.y = 0;
temp->clone.map = NULL;
if (FABS(temp->clone.speed) > MIN_ACTIVE_SPEED) {
/* Clone has a speed, so need to clear that because it isn't on a map.
* But we need to keep the value, because otherwise the customized object
* will have no speed (fix_player() will use the 0 value). So set it
* to zero, call update_ob_speed() to remove it from active list, then
* set its speed back to the original.
*/
temp->clone.speed = 0;
update_ob_speed(&temp->clone);
temp->clone.speed = op->speed;
}
temp->more = op->arch->more;
op->arch = temp;
/* LOG(llevDebug, "created temporary archetype for %s at %d,%d\n", op->name, op->x, op->y); */
}
}
START_TEST(test_randommaps) {
#if 0
int test;
mapstruct *overlay;
object *check;
for (test = 0; test < 50; test++)
do_run();
for (test = 0; test < 50; test++) {
overlay = ready_map_name("../../rsc/bug_1727944_unique", MAP_PLAYER_UNIQUE);
fail_unless(overlay != NULL, "Couldn't load unique map ../../rsc/bug_1727944_unique");
fail_unless(GET_MAP_OB(overlay, 2, 3) != NULL, "No item on spot 2,3?");
for (check = GET_MAP_OB(overlay, 2, 3)->above; check != NULL; check = check->above) {
fail_unless(check->type == HORN, "Found a non horn?");
fail_unless(check->inv != NULL, "Horn without a spell!");
fail_unless(check->inv->below == NULL, "Horn with 2 items in inventory.");
}
save_map(overlay, SAVE_MODE_OVERLAY);
delete_map(overlay);
}
#endif
#if 0
int test;
archetype *horn = find_archetype("horn");
fail_unless(horn != NULL, "couldn't find archetype horn.");
archetype *horn2 = find_archetype("horn2");
fail_unless(horn2 != NULL, "couldn't find archetype horn2.");
for (test = 0; test < 100000; test++) {
object *check = arch_to_object(RANDOM()%2 ? horn : horn2);
generate_artifact(check, RANDOM()%100);
fail_unless(check->inv != NULL, "horn without inventory!");
}
#endif
int test, level, found = 0;
object *the_chest, *check;
mapstruct *map;
treasurelist *tlist = find_treasurelist("uncommon_items");
fail_unless(tlist != NULL, "couldn't find treasure list uncommon_items");
for (test = 0; test < 10; test++) {
for (level = 1; level < 120; level++) {
map = get_empty_map(1, 1);
fail_unless(map != NULL, "failed to get empty map");
map->difficulty = level;
the_chest = create_archetype("chest"); /* was "chest_2" */
fail_unless(the_chest != NULL, "failed to get chest");
the_chest->randomitems = tlist;
the_chest->stats.hp = RANDOM()%100;
insert_ob_in_map_at(the_chest, map, NULL, 0, 0, 0);
fix_auto_apply(map);
the_chest = GET_MAP_OB(map, 0, 0);
fail_unless(the_chest != NULL, "failed to recover chest?");
for (check = the_chest->inv; check; check = check->below) {
if (check->type != HORN)
continue;
local_check_loaded_object(check);
fail_unless(check->inv != NULL, "horn without inventory");
fail_unless(check->inv->below == NULL, "horn with 2 items");
fail_unless(check->randomitems == NULL, "horn with randomitems set");
found++;
}
delete_map(map);
}
}
fail_unless(found > 100, "didn't find 100 horn but %d??", found);
}
END_TEST
Suite *bug_suite(void) {
Suite *s = suite_create("bug");
TCase *tc_core = tcase_create("Core");
/*setup and teardown will be called before each test in testcase 'tc_core' */
tcase_add_checked_fixture(tc_core, setup, teardown);
suite_add_tcase(s, tc_core);
tcase_add_test(tc_core, test_randommaps);
tcase_set_timeout(tc_core, 0);
return s;
}
int main(void) {
int nf;
Suite *s = bug_suite();
SRunner *sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
init(0, NULL);
srunner_set_xml(sr, LOGDIR "/bugs/bugtrack/1727944.xml");
srunner_set_log(sr, LOGDIR "/bugs/bugtrack/1727944.out");
srunner_run_all(sr, CK_ENV); /*verbosity from env variable*/
nf = srunner_ntests_failed(sr);
srunner_free(sr);
return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}