transmission/libtransmission/rename-test.c

591 lines
19 KiB
C
Raw Normal View History

/*
* This file Copyright (C) 2013-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h> /* fopen() */
#include <string.h> /* strcmp() */
#include "transmission.h"
#include "crypto-utils.h"
#include "file.h"
#include "resume.h"
#include "torrent.h" /* tr_isTorrent() */
#include "variant.h"
#include "libtransmission-test.h"
/***
****
***/
static tr_session* session = NULL;
#define check_have_none(tor, totalSize) \
do \
{ \
const tr_stat* tst = tr_torrentStat(tor); \
check_int_eq(TR_STATUS_STOPPED, tst->activity); \
check_int_eq(TR_STAT_OK, tst->error); \
check_uint_eq(totalSize, tst->sizeWhenDone); \
check_uint_eq(totalSize, tst->leftUntilDone); \
check_uint_eq(totalSize, tor->info.totalSize); \
check_uint_eq(0, tst->haveValid); \
} \
while (0)
static bool testFileExistsAndConsistsOfThisString(const tr_torrent* tor, tr_file_index_t fileIndex, const char* str)
{
char* path;
const size_t str_len = strlen(str);
bool success = false;
path = tr_torrentFindFile(tor, fileIndex);
if (path != NULL)
{
uint8_t* contents;
size_t contents_len;
assert(tr_sys_path_exists(path, NULL));
contents = tr_loadFile(path, &contents_len, NULL);
success = contents != NULL && str_len == contents_len && memcmp(contents, str, contents_len) == 0;
tr_free(contents);
tr_free(path);
}
return success;
}
static void onRenameDone(tr_torrent* tor UNUSED, const char* oldpath UNUSED, const char* newname UNUSED, int error,
void* user_data)
{
*(int*)user_data = error;
}
static int torrentRenameAndWait(tr_torrent* tor, const char* oldpath, const char* newname)
{
int error = -1;
tr_torrentRenamePath(tor, oldpath, newname, onRenameDone, &error);
do
{
tr_wait_msec(10);
}
while (error == -1);
return error;
}
static void torrentRemoveAndWait(tr_torrent* tor, int expected_torrent_count)
{
tr_torrentRemove(tor, false, NULL);
while (tr_sessionCountTorrents(session) != expected_torrent_count)
{
tr_wait_msec(10);
}
}
/***
****
***/
static void create_single_file_torrent_contents(const char* top)
{
char* path = tr_buildPath(top, "hello-world.txt", NULL);
libtest_create_file_with_string_contents(path, "hello, world!\n");
tr_free(path);
}
static tr_torrent* create_torrent_from_base64_metainfo(tr_ctor* ctor, const char* metainfo_base64)
{
int err;
size_t metainfo_len;
char* metainfo;
tr_torrent* tor;
/* create the torrent ctor */
metainfo = tr_base64_decode_str(metainfo_base64, &metainfo_len);
assert(metainfo != NULL);
assert(metainfo_len > 0);
tr_ctorSetMetainfo(ctor, (uint8_t*)metainfo, metainfo_len);
tr_ctorSetPaused(ctor, TR_FORCE, true);
/* create the torrent */
err = 0;
tor = tr_torrentNew(ctor, &err, NULL);
assert(!err);
/* cleanup */
tr_free(metainfo);
return tor;
}
static int test_single_filename_torrent(void)
{
uint64_t loaded;
tr_torrent* tor;
char* tmpstr;
const size_t totalSize = 14;
tr_ctor* ctor;
const tr_stat* st;
/* this is a single-file torrent whose file is hello-world.txt, holding the string "hello, world!" */
ctor = tr_ctorNew(session);
tor = create_torrent_from_base64_metainfo(ctor,
"ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0"
"ZWkxMzU4NTQ5MDk4ZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDY6bGVuZ3RoaTE0ZTQ6bmFtZTE1"
"OmhlbGxvLXdvcmxkLnR4dDEyOnBpZWNlIGxlbmd0aGkzMjc2OGU2OnBpZWNlczIwOukboJcrkFUY"
"f6LvqLXBVvSHqCk6Nzpwcml2YXRlaTBlZWU=");
check(tr_isTorrent(tor));
/* sanity check the info */
check_int_eq(1, tor->info.fileCount);
check_streq("hello-world.txt", tor->info.files[0].name);
check(!tor->info.files[0].is_renamed);
/* sanity check the (empty) stats */
libttest_blockingTorrentVerify(tor);
check_have_none(tor, totalSize);
create_single_file_torrent_contents(tor->currentDir);
/* sanity check the stats again, now that we've added the file */
libttest_blockingTorrentVerify(tor);
st = tr_torrentStat(tor);
check_int_eq(TR_STATUS_STOPPED, st->activity);
check_int_eq(TR_STAT_OK, st->error);
check_uint_eq(0, st->leftUntilDone);
check_uint_eq(0, st->haveUnchecked);
check_uint_eq(0, st->desiredAvailable);
check_uint_eq(totalSize, st->sizeWhenDone);
check_uint_eq(totalSize, st->haveValid);
/**
*** okay! we've finally put together all the scaffolding to test
*** renaming a single-file torrent
**/
/* confirm that bad inputs get caught */
check_int_eq(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", NULL));
check_int_eq(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", ""));
check_int_eq(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", "."));
check_int_eq(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", ".."));
check_int_eq(0, torrentRenameAndWait(tor, "hello-world.txt", "hello-world.txt"));
check_int_eq(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", "hello/world.txt"));
check(!tor->info.files[0].is_renamed);
check_streq("hello-world.txt", tor->info.files[0].name);
/***
**** Now try a rename that should succeed
***/
tmpstr = tr_buildPath(tor->currentDir, "hello-world.txt", NULL);
check(tr_sys_path_exists(tmpstr, NULL));
check_streq("hello-world.txt", tr_torrentName(tor));
check_int_eq(0, torrentRenameAndWait(tor, tor->info.name, "foobar"));
check(!tr_sys_path_exists(tmpstr, NULL)); /* confirm the old filename can't be found */
tr_free(tmpstr);
check(tor->info.files[0].is_renamed); /* confirm the file's 'renamed' flag is set */
check_streq("foobar", tr_torrentName(tor)); /* confirm the torrent's name is now 'foobar' */
check_streq("foobar", tor->info.files[0].name); /* confirm the file's name is now 'foobar' in our struct */
check(strstr(tor->info.torrent, "foobar") == NULL); /* confirm the name in the .torrent file hasn't changed */
tmpstr = tr_buildPath(tor->currentDir, "foobar", NULL);
check(tr_sys_path_exists(tmpstr, NULL)); /* confirm the file's name is now 'foobar' on the disk */
tr_free(tmpstr);
check(testFileExistsAndConsistsOfThisString(tor, 0, "hello, world!\n")); /* confirm the contents are right */
/* (while it's renamed: confirm that the .resume file remembers the changes) */
tr_torrentSaveResume(tor);
libttest_sync();
loaded = tr_torrentLoadResume(tor, ~0, ctor);
check_streq("foobar", tr_torrentName(tor));
check((loaded & TR_FR_NAME) != 0);
/***
**** ...and rename it back again
***/
tmpstr = tr_buildPath(tor->currentDir, "foobar", NULL);
check(tr_sys_path_exists(tmpstr, NULL));
check_int_eq(0, torrentRenameAndWait(tor, "foobar", "hello-world.txt"));
check(!tr_sys_path_exists(tmpstr, NULL));
check(tor->info.files[0].is_renamed);
check_streq("hello-world.txt", tor->info.files[0].name);
check_streq("hello-world.txt", tr_torrentName(tor));
tr_free(tmpstr);
check(testFileExistsAndConsistsOfThisString(tor, 0, "hello, world!\n"));
/* cleanup */
tr_ctorFree(ctor);
torrentRemoveAndWait(tor, 0);
return 0;
}
/***
****
****
****
***/
static void create_multifile_torrent_contents(const char* top)
{
char* path;
path = tr_buildPath(top, "Felidae", "Felinae", "Acinonyx", "Cheetah", "Chester", NULL);
libtest_create_file_with_string_contents(path, "It ain't easy bein' cheesy.\n");
tr_free(path);
path = tr_buildPath(top, "Felidae", "Pantherinae", "Panthera", "Tiger", "Tony", NULL);
libtest_create_file_with_string_contents(path, "Theyre Grrrrreat!\n");
tr_free(path);
path = tr_buildPath(top, "Felidae", "Felinae", "Felis", "catus", "Kyphi", NULL);
libtest_create_file_with_string_contents(path, "Inquisitive\n");
tr_free(path);
path = tr_buildPath(top, "Felidae", "Felinae", "Felis", "catus", "Saffron", NULL);
libtest_create_file_with_string_contents(path, "Tough\n");
tr_free(path);
libttest_sync();
}
static int test_multifile_torrent(void)
{
tr_file_index_t i;
uint64_t loaded;
tr_torrent* tor;
tr_ctor* ctor;
char* str;
char* tmp;
static const size_t totalSize = 67;
const tr_stat* st;
const tr_file* files;
const char* strings[4];
const char* expected_files[4] =
{
"Felidae/Felinae/Acinonyx/Cheetah/Chester",
"Felidae/Felinae/Felis/catus/Kyphi",
"Felidae/Felinae/Felis/catus/Saffron",
"Felidae/Pantherinae/Panthera/Tiger/Tony"
};
const char* expected_contents[4] =
{
"It ain't easy bein' cheesy.\n",
"Inquisitive\n",
"Tough\n",
"Theyre Grrrrreat!\n"
};
ctor = tr_ctorNew(session);
tor = create_torrent_from_base64_metainfo(ctor,
"ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0"
"ZWkxMzU4NTU1NDIwZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDU6ZmlsZXNsZDY6bGVuZ3RoaTI4"
"ZTQ6cGF0aGw3OkZlbGluYWU4OkFjaW5vbnl4NzpDaGVldGFoNzpDaGVzdGVyZWVkNjpsZW5ndGhp"
"MTJlNDpwYXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNTpLeXBoaWVlZDY6bGVuZ3RoaTZlNDpw"
"YXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNzpTYWZmcm9uZWVkNjpsZW5ndGhpMjFlNDpwYXRo"
"bDExOlBhbnRoZXJpbmFlODpQYW50aGVyYTU6VGlnZXI0OlRvbnllZWU0Om5hbWU3OkZlbGlkYWUx"
"MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp"
"dmF0ZWkwZWVl");
check(tr_isTorrent(tor));
files = tor->info.files;
/* sanity check the info */
check_streq(tor->info.name, "Felidae");
check_uint_eq(totalSize, tor->info.totalSize);
check_uint_eq(4, tor->info.fileCount);
for (i = 0; i < 4; ++i)
{
check_streq(expected_files[i], files[i].name);
}
/* sanity check the (empty) stats */
libttest_blockingTorrentVerify(tor);
check_have_none(tor, totalSize);
/* build the local data */
create_multifile_torrent_contents(tor->currentDir);
/* sanity check the (full) stats */
libttest_blockingTorrentVerify(tor);
st = tr_torrentStat(tor);
check_int_eq(TR_STATUS_STOPPED, st->activity);
check_int_eq(TR_STAT_OK, st->error);
check_uint_eq(0, st->leftUntilDone);
check_uint_eq(0, st->haveUnchecked);
check_uint_eq(0, st->desiredAvailable);
check_uint_eq(totalSize, st->sizeWhenDone);
check_uint_eq(totalSize, st->haveValid);
/**
*** okay! let's test renaming.
**/
/* rename a leaf... */
check_int_eq(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus/Kyphi", "placeholder"));
check_streq(files[1].name, "Felidae/Felinae/Felis/catus/placeholder");
check(testFileExistsAndConsistsOfThisString(tor, 1, "Inquisitive\n"));
/* ...and back again */
check_int_eq(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus/placeholder", "Kyphi"));
check_streq(files[1].name, "Felidae/Felinae/Felis/catus/Kyphi");
testFileExistsAndConsistsOfThisString(tor, 1, "Inquisitive\n");
/* rename a branch... */
check_int_eq(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus", "placeholder"));
check_streq(expected_files[0], files[0].name);
check_streq("Felidae/Felinae/Felis/placeholder/Kyphi", files[1].name);
check_streq("Felidae/Felinae/Felis/placeholder/Saffron", files[2].name);
check_streq(expected_files[3], files[3].name);
check(testFileExistsAndConsistsOfThisString(tor, 1, expected_contents[1]));
check(testFileExistsAndConsistsOfThisString(tor, 2, expected_contents[2]));
check(files[0].is_renamed == false);
check(files[1].is_renamed == true);
check(files[2].is_renamed == true);
check(files[3].is_renamed == false);
/* (while the branch is renamed: confirm that the .resume file remembers the changes) */
tr_torrentSaveResume(tor);
/* this is a bit dodgy code-wise, but let's make sure the .resume file got the name */
tr_free(files[1].name);
tor->info.files[1].name = tr_strdup("gabba gabba hey");
loaded = tr_torrentLoadResume(tor, ~0, ctor);
check((loaded & TR_FR_FILENAMES) != 0);
check_streq(expected_files[0], files[0].name);
check_streq("Felidae/Felinae/Felis/placeholder/Kyphi", files[1].name);
check_streq("Felidae/Felinae/Felis/placeholder/Saffron", files[2].name);
check_streq(expected_files[3], files[3].name);
/* ...and back again */
check_int_eq(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/placeholder", "catus"));
for (i = 0; i < 4; ++i)
{
check_streq(expected_files[i], files[i].name);
check(testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i]));
}
check(files[0].is_renamed == false);
check(files[1].is_renamed == true);
check(files[2].is_renamed == true);
check(files[3].is_renamed == false);
/***
**** Test it an incomplete torrent...
***/
/* remove the directory Felidae/Felinae/Felis/catus */
str = tr_torrentFindFile(tor, 1);
check(str != NULL);
tr_sys_path_remove(str, NULL);
tr_free(str);
str = tr_torrentFindFile(tor, 2);
check(str != NULL);
tr_sys_path_remove(str, NULL);
tmp = tr_sys_path_dirname(str, NULL);
tr_sys_path_remove(tmp, NULL);
tr_free(tmp);
tr_free(str);
libttest_sync();
libttest_blockingTorrentVerify(tor);
testFileExistsAndConsistsOfThisString(tor, 0, expected_contents[0]);
for (i = 1; i <= 2; ++i)
{
str = tr_torrentFindFile(tor, i);
check_streq(NULL, str);
tr_free(str);
}
testFileExistsAndConsistsOfThisString(tor, 3, expected_contents[3]);
/* rename a branch... */
check_int_eq(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus", "foo"));
check_streq(expected_files[0], files[0].name);
check_streq("Felidae/Felinae/Felis/foo/Kyphi", files[1].name);
check_streq("Felidae/Felinae/Felis/foo/Saffron", files[2].name);
check_streq(expected_files[3], files[3].name);
/* ...and back again */
check_int_eq(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/foo", "catus"));
for (i = 0; i < 4; ++i)
{
check_streq(expected_files[i], files[i].name);
}
check_int_eq(0, torrentRenameAndWait(tor, "Felidae", "gabba"));
strings[0] = "gabba/Felinae/Acinonyx/Cheetah/Chester";
strings[1] = "gabba/Felinae/Felis/catus/Kyphi";
strings[2] = "gabba/Felinae/Felis/catus/Saffron";
strings[3] = "gabba/Pantherinae/Panthera/Tiger/Tony";
for (i = 0; i < 4; ++i)
{
check_streq(strings[i], files[i].name);
testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i]);
}
/* rename the root, then a branch, and then a leaf... */
check_int_eq(0, torrentRenameAndWait(tor, "gabba", "Felidae"));
check_int_eq(0, torrentRenameAndWait(tor, "Felidae/Pantherinae/Panthera/Tiger", "Snow Leopard"));
check_int_eq(0, torrentRenameAndWait(tor, "Felidae/Pantherinae/Panthera/Snow Leopard/Tony", "10.6"));
strings[0] = "Felidae/Felinae/Acinonyx/Cheetah/Chester";
strings[1] = "Felidae/Felinae/Felis/catus/Kyphi";
strings[2] = "Felidae/Felinae/Felis/catus/Saffron";
strings[3] = "Felidae/Pantherinae/Panthera/Snow Leopard/10.6";
for (i = 0; i < 4; ++i)
{
check_streq(strings[i], files[i].name);
testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i]);
}
tr_ctorFree(ctor);
torrentRemoveAndWait(tor, 0);
/**
*** Test renaming prefixes (shouldn't work)
**/
ctor = tr_ctorNew(session);
tor = create_torrent_from_base64_metainfo(ctor,
"ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0"
"ZWkxMzU4NTU1NDIwZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDU6ZmlsZXNsZDY6bGVuZ3RoaTI4"
"ZTQ6cGF0aGw3OkZlbGluYWU4OkFjaW5vbnl4NzpDaGVldGFoNzpDaGVzdGVyZWVkNjpsZW5ndGhp"
"MTJlNDpwYXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNTpLeXBoaWVlZDY6bGVuZ3RoaTZlNDpw"
"YXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNzpTYWZmcm9uZWVkNjpsZW5ndGhpMjFlNDpwYXRo"
"bDExOlBhbnRoZXJpbmFlODpQYW50aGVyYTU6VGlnZXI0OlRvbnllZWU0Om5hbWU3OkZlbGlkYWUx"
"MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp"
"dmF0ZWkwZWVl");
check(tr_isTorrent(tor));
files = tor->info.files;
/* rename prefix of top */
check_int_eq(EINVAL, torrentRenameAndWait(tor, "Feli", "FelidaeX"));
check_streq(tor->info.name, "Felidae");
check(files[0].is_renamed == false);
check(files[1].is_renamed == false);
check(files[2].is_renamed == false);
check(files[3].is_renamed == false);
/* rename false path */
check_int_eq(EINVAL, torrentRenameAndWait(tor, "Felidae/FelinaeX", "Genus Felinae"));
check_streq(tor->info.name, "Felidae");
check(files[0].is_renamed == false);
check(files[1].is_renamed == false);
check(files[2].is_renamed == false);
check(files[3].is_renamed == false);
/***
****
***/
/* cleanup */
tr_ctorFree(ctor);
torrentRemoveAndWait(tor, 0);
return 0;
}
/***
****
***/
static int test_partial_file(void)
{
tr_file_index_t i;
tr_torrent* tor;
const tr_stat* st;
tr_file_stat* fst;
const uint32_t pieceCount = 33;
const uint32_t pieceSize = 32768;
const uint32_t length[] = { 1048576, 4096, 512 };
const uint64_t totalSize = length[0] + length[1] + length[2];
const char* strings[3];
/***
**** create our test torrent with an incomplete .part file
***/
tor = libttest_zero_torrent_init(session);
check_uint_eq(totalSize, tor->info.totalSize);
check_uint_eq(pieceSize, tor->info.pieceSize);
check_uint_eq(pieceCount, tor->info.pieceCount);
check_streq("files-filled-with-zeroes/1048576", tor->info.files[0].name);
check_streq("files-filled-with-zeroes/4096", tor->info.files[1].name);
check_streq("files-filled-with-zeroes/512", tor->info.files[2].name);
libttest_zero_torrent_populate(tor, false);
fst = tr_torrentFiles(tor, NULL);
check_uint_eq(length[0] - pieceSize, fst[0].bytesCompleted);
check_uint_eq(length[1], fst[1].bytesCompleted);
check_uint_eq(length[2], fst[2].bytesCompleted);
tr_torrentFilesFree(fst, tor->info.fileCount);
st = tr_torrentStat(tor);
check_uint_eq(totalSize, st->sizeWhenDone);
check_uint_eq(pieceSize, st->leftUntilDone);
/***
****
***/
check_int_eq(0, torrentRenameAndWait(tor, "files-filled-with-zeroes", "foo"));
check_int_eq(0, torrentRenameAndWait(tor, "foo/1048576", "bar"));
strings[0] = "foo/bar";
strings[1] = "foo/4096";
strings[2] = "foo/512";
for (i = 0; i < 3; ++i)
{
check_streq(strings[i], tor->info.files[i].name);
}
strings[0] = "foo/bar.part";
for (i = 0; i < 3; ++i)
{
char* expected = tr_buildPath(tor->currentDir, strings[i], NULL);
char* path = tr_torrentFindFile(tor, i);
check_streq(expected, path);
tr_free(path);
tr_free(expected);
}
torrentRemoveAndWait(tor, 0);
return 0;
}
/***
****
***/
int main(void)
{
int ret;
const testFunc tests[] =
{
test_single_filename_torrent,
test_multifile_torrent,
test_partial_file
};
session = libttest_session_init(NULL);
ret = runTests(tests, NUM_TESTS(tests));
libttest_session_close(session);
return ret;
}