source: trunk/libtransmission/rename-test.c @ 13906

Last change on this file since 13906 was 13906, checked in by jordan, 8 years ago

fix a race condition error in rename-test

File size: 17.5 KB
Line 
1#include <assert.h>
2#include <errno.h>
3#include <stdio.h> /* remove() */
4#include <string.h> /* strcmp() */
5#include <stdio.h>
6
7#include <sys/types.h> /* stat() */
8#include <sys/stat.h> /* stat() */
9#include <unistd.h> /* stat(), sync() */
10
11#include "transmission.h"
12#include "resume.h"
13#include "torrent.h" /* tr_isTorrent() */
14#include "utils.h" /* tr_mkdirp() */
15#include "variant.h"
16
17#include "libtransmission-test.h"
18
19/***
20****
21***/
22
23#define verify_and_block_until_done(tor) \
24  do { \
25    do { tr_wait_msec (10); } while (tor->verifyState != TR_VERIFY_NONE); \
26    tr_torrentVerify (tor); \
27    do { tr_wait_msec (10); } while (tor->verifyState != TR_VERIFY_NONE); \
28  } while (0)
29
30#define check_have_none(tor, totalSize) \
31  do { \
32    const tr_stat * st = tr_torrentStat(tor); \
33    check_int_eq (TR_STATUS_STOPPED, st->activity); \
34    check_int_eq (TR_STAT_OK, st->error); \
35    check_int_eq (totalSize, st->sizeWhenDone); \
36    check_int_eq (totalSize, st->leftUntilDone); \
37    check_int_eq (totalSize, tor->info.totalSize); \
38    check_int_eq (0, st->haveValid); \
39  } while (0)
40
41static bool
42testFileExistsAndConsistsOfThisString (const tr_torrent * tor, tr_file_index_t fileIndex, const char * str)
43{
44  char * path;
45  const size_t str_len = strlen (str);
46  bool success = false;
47
48  path = tr_torrentFindFile (tor, fileIndex);
49  if (path != NULL)
50    {
51      uint8_t * contents;
52      size_t contents_len;
53
54      assert (tr_fileExists (path, NULL));
55
56      contents = tr_loadFile (path, &contents_len);
57
58      success = (str_len == contents_len)
59             && (!memcmp (contents, str, contents_len));
60
61      tr_free (contents);
62      tr_free (path);
63    }
64
65  return success;
66}
67
68static void
69onRenameDone (tr_torrent * tor UNUSED, const char * oldpath UNUSED, const char * newname UNUSED, int error, void * user_data)
70{
71  *(int*)user_data = error;
72}
73
74static int
75torrentRenameAndWait (tr_torrent * tor,
76                      const char * oldpath,
77                      const char * newname)
78{
79  int error = -1;
80  tr_torrentRenamePath (tor, oldpath, newname, onRenameDone, &error);
81  do {
82    tr_wait_msec (10);
83  } while (error == -1);
84  return error;
85}
86
87/***
88****
89***/
90
91static void
92create_file_with_contents (const char * path, const char * str)
93{
94  int rv;
95  FILE * fp;
96  char * dir;
97  const int tmperr = errno;
98
99  dir = tr_dirname (path);
100  errno = 0;
101  rv = tr_mkdirp (dir, 0700);
102  assert (errno == 0);
103  assert (rv == 0);
104  tr_free (dir);
105
106  remove (path);
107  fp = fopen (path, "wb");
108  fprintf (fp, "%s", str);
109  fclose (fp);
110
111  sync ();
112
113  errno = tmperr;
114}
115
116static void
117create_single_file_torrent_contents (const char * top)
118{
119  char * path = tr_buildPath (top, "hello-world.txt", NULL);
120  create_file_with_contents (path, "hello, world!\n");
121  tr_free (path);
122}
123
124static tr_torrent *
125create_torrent_from_base64_metainfo (tr_ctor * ctor, const char * metainfo_base64)
126{
127  int err;
128  int metainfo_len;
129  char * metainfo;
130  tr_torrent * tor;
131
132  /* create the torrent ctor */
133  metainfo = tr_base64_decode (metainfo_base64, -1, &metainfo_len);
134  assert (metainfo != NULL);
135  assert (metainfo_len > 0);
136  assert (session != NULL);
137  tr_ctorSetMetainfo (ctor, (uint8_t*)metainfo, metainfo_len);
138  tr_ctorSetPaused (ctor, TR_FORCE, true);
139
140  /* create the torrent */
141  err = 0;
142  tor = tr_torrentNew (ctor, &err);
143  assert (!err);
144
145  /* cleanup */
146  tr_free (metainfo); 
147  return tor;
148}
149
150static int
151test_single_filename_torrent (void)
152{
153  uint64_t loaded;
154  tr_torrent * tor;
155  char * tmpstr;
156  const size_t totalSize = 14;
157  tr_ctor * ctor;
158  const tr_stat * st;
159
160  /* this is a single-file torrent whose file is hello-world.txt, holding the string "hello, world!" */
161  ctor = tr_ctorNew (session);
162  tor = create_torrent_from_base64_metainfo (ctor,
163    "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0"
164    "ZWkxMzU4NTQ5MDk4ZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDY6bGVuZ3RoaTE0ZTQ6bmFtZTE1"
165    "OmhlbGxvLXdvcmxkLnR4dDEyOnBpZWNlIGxlbmd0aGkzMjc2OGU2OnBpZWNlczIwOukboJcrkFUY"
166    "f6LvqLXBVvSHqCk6Nzpwcml2YXRlaTBlZWU=");
167  check (tr_isTorrent (tor));
168
169  /* sanity check the info */
170  check_int_eq (1, tor->info.fileCount);
171  check_streq ("hello-world.txt", tor->info.files[0].name);
172  check (!tor->info.files[0].is_renamed);
173
174  /* sanity check the (empty) stats */
175  verify_and_block_until_done (tor);
176  check_have_none (tor, totalSize);
177
178  create_single_file_torrent_contents (tor->currentDir);
179
180  /* sanity check the stats again, now that we've added the file */
181  verify_and_block_until_done (tor);
182  st = tr_torrentStat (tor);
183  check_int_eq (TR_STATUS_STOPPED, st->activity);
184  check_int_eq (TR_STAT_OK, st->error);
185  check_int_eq (0, st->leftUntilDone);
186  check_int_eq (0, st->haveUnchecked);
187  check_int_eq (0, st->desiredAvailable);
188  check_int_eq (totalSize, st->sizeWhenDone);
189  check_int_eq (totalSize, st->haveValid);
190
191  /**
192  ***  okay! we've finally put together all the scaffolding to test
193  ***  renaming a single-file torrent
194  **/
195
196  /* confirm that bad inputs get caught */
197
198  check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", NULL));
199  check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", ""));
200  check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", "."));
201  check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", ".."));
202  check_int_eq (0, torrentRenameAndWait (tor, "hello-world.txt", "hello-world.txt"));
203  check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", "hello/world.txt"));
204
205  check (!tor->info.files[0].is_renamed);
206  check_streq ("hello-world.txt", tor->info.files[0].name);
207
208  /***
209  ****  Now try a rename that should succeed
210  ***/
211
212  tmpstr = tr_buildPath (tor->currentDir, "hello-world.txt", NULL); 
213  check (tr_fileExists (tmpstr, NULL));
214  check_streq ("hello-world.txt", tr_torrentName(tor));
215  check_int_eq (0, torrentRenameAndWait (tor, tor->info.name, "foobar"));
216  check (!tr_fileExists (tmpstr, NULL)); /* confirm the old filename can't be found */
217  tr_free (tmpstr);
218  check (tor->info.files[0].is_renamed); /* confirm the file's 'renamed' flag is set */
219  check_streq ("foobar", tr_torrentName(tor)); /* confirm the torrent's name is now 'foobar' */
220  check_streq ("foobar", tor->info.files[0].name); /* confirm the file's name is now 'foobar' in our struct */
221  check (strstr (tor->info.torrent, "foobar") == NULL); /* confirm the name in the .torrent file hasn't changed */
222  tmpstr = tr_buildPath (tor->currentDir, "foobar", NULL); 
223  check (tr_fileExists (tmpstr, NULL)); /* confirm the file's name is now 'foobar' on the disk */
224  tr_free (tmpstr);
225  check (testFileExistsAndConsistsOfThisString (tor, 0, "hello, world!\n")); /* confirm the contents are right */
226
227  /* (while it's renamed: confirm that the .resume file remembers the changes) */
228  tr_torrentSaveResume (tor);
229  sync ();
230  loaded = tr_torrentLoadResume (tor, ~0, ctor);
231  check_streq ("foobar", tr_torrentName(tor));
232  check ((loaded & TR_FR_NAME) != 0);
233
234  /***
235  ****  ...and rename it back again
236  ***/
237
238  tmpstr = tr_buildPath (tor->currentDir, "foobar", NULL); 
239  check (tr_fileExists (tmpstr, NULL));
240  check_int_eq (0, torrentRenameAndWait (tor, "foobar", "hello-world.txt"));
241  check (!tr_fileExists (tmpstr, NULL));
242  check (tor->info.files[0].is_renamed);
243  check_streq ("hello-world.txt", tor->info.files[0].name);
244  check_streq ("hello-world.txt", tr_torrentName(tor));
245  tr_free (tmpstr);
246  check (testFileExistsAndConsistsOfThisString (tor, 0, "hello, world!\n"));
247
248  /* cleanup */
249  tr_ctorFree (ctor);
250  tr_torrentRemove (tor, false, NULL);
251  return 0;
252}
253
254/***
255****
256****
257****
258***/
259
260static void
261create_multifile_torrent_contents (const char * top)
262{
263  char * path;
264
265  path = tr_buildPath (top, "Felidae", "Felinae", "Acinonyx", "Cheetah", "Chester", NULL);
266  create_file_with_contents (path, "It ain't easy bein' cheesy.\n");
267  tr_free (path);
268
269  path = tr_buildPath (top, "Felidae", "Pantherinae", "Panthera", "Tiger", "Tony", NULL);
270  create_file_with_contents (path, "They’re Grrrrreat!\n");
271  tr_free (path);
272
273  path = tr_buildPath (top, "Felidae", "Felinae", "Felis", "catus", "Kyphi", NULL);
274  create_file_with_contents (path, "Inquisitive\n");
275  tr_free (path);
276
277  path = tr_buildPath (top, "Felidae", "Felinae", "Felis", "catus", "Saffron", NULL);
278  create_file_with_contents (path, "Tough\n");
279  tr_free (path);
280
281  sync ();
282}
283
284static int
285test_multifile_torrent (void)
286{
287  tr_file_index_t i;
288  uint64_t loaded;
289  tr_torrent * tor;
290  tr_ctor * ctor;
291  char * str;
292  char * tmp;
293  static const size_t totalSize = 67;
294  const tr_stat * st;
295  const tr_file * files;
296  const char * strings[4];
297  const char * expected_files[4] = {
298    "Felidae/Felinae/Acinonyx/Cheetah/Chester",
299    "Felidae/Felinae/Felis/catus/Kyphi",
300    "Felidae/Felinae/Felis/catus/Saffron",
301    "Felidae/Pantherinae/Panthera/Tiger/Tony"
302  };
303  const char * expected_contents[4] = {
304   "It ain't easy bein' cheesy.\n",
305   "Inquisitive\n",
306   "Tough\n",
307   "They’re Grrrrreat!\n"
308  };
309
310  ctor = tr_ctorNew (session);
311  tor = create_torrent_from_base64_metainfo (ctor,
312    "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0"
313    "ZWkxMzU4NTU1NDIwZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDU6ZmlsZXNsZDY6bGVuZ3RoaTI4"
314    "ZTQ6cGF0aGw3OkZlbGluYWU4OkFjaW5vbnl4NzpDaGVldGFoNzpDaGVzdGVyZWVkNjpsZW5ndGhp"
315    "MTJlNDpwYXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNTpLeXBoaWVlZDY6bGVuZ3RoaTZlNDpw"
316    "YXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNzpTYWZmcm9uZWVkNjpsZW5ndGhpMjFlNDpwYXRo"
317    "bDExOlBhbnRoZXJpbmFlODpQYW50aGVyYTU6VGlnZXI0OlRvbnllZWU0Om5hbWU3OkZlbGlkYWUx"
318    "MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp"
319    "dmF0ZWkwZWVl");
320  check (tr_isTorrent (tor));
321  files = tor->info.files;
322
323  /* sanity check the info */
324  check_streq (tor->info.name, "Felidae");
325  check_int_eq (totalSize, tor->info.totalSize);
326  check_int_eq (4, tor->info.fileCount);
327  for (i=0; i<4; ++i)
328    check_streq (expected_files[i], files[i].name);
329
330  /* sanity check the (empty) stats */
331  verify_and_block_until_done (tor);
332  check_have_none (tor, totalSize);
333
334  /* build the local data */
335  create_multifile_torrent_contents (tor->currentDir);
336
337  /* sanity check the (full) stats */
338  verify_and_block_until_done (tor);
339  st = tr_torrentStat (tor);
340  check_int_eq (TR_STATUS_STOPPED, st->activity);
341  check_int_eq (TR_STAT_OK, st->error);
342  check_int_eq (0, st->leftUntilDone);
343  check_int_eq (0, st->haveUnchecked);
344  check_int_eq (0, st->desiredAvailable);
345  check_int_eq (totalSize, st->sizeWhenDone);
346  check_int_eq (totalSize, st->haveValid);
347
348
349  /**
350  ***  okay! let's test renaming.
351  **/
352
353  /* rename a leaf... */
354  check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/catus/Kyphi", "placeholder"));
355  check_streq (files[1].name, "Felidae/Felinae/Felis/catus/placeholder");
356  check (testFileExistsAndConsistsOfThisString (tor, 1, "Inquisitive\n"));
357
358  /* ...and back again */
359  check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/catus/placeholder", "Kyphi"));
360  check_streq (files[1].name, "Felidae/Felinae/Felis/catus/Kyphi");
361  testFileExistsAndConsistsOfThisString (tor, 1, "Inquisitive\n");
362
363  /* rename a branch... */
364  check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/catus", "placeholder"));
365  check_streq (expected_files[0],                           files[0].name);
366  check_streq ("Felidae/Felinae/Felis/placeholder/Kyphi",   files[1].name);
367  check_streq ("Felidae/Felinae/Felis/placeholder/Saffron", files[2].name);
368  check_streq (expected_files[3],                           files[3].name);
369  check (testFileExistsAndConsistsOfThisString (tor, 1, expected_contents[1]));
370  check (testFileExistsAndConsistsOfThisString (tor, 2, expected_contents[2]));
371  check (files[0].is_renamed == false);
372  check (files[1].is_renamed == true);
373  check (files[2].is_renamed == true);
374  check (files[3].is_renamed == false);
375
376  /* (while the branch is renamed: confirm that the .resume file remembers the changes) */
377  tr_torrentSaveResume (tor);
378  /* this is a bit dodgy code-wise, but let's make sure the .resume file got the name */
379  tr_free (files[1].name);
380  tor->info.files[1].name = tr_strdup ("gabba gabba hey");
381  loaded = tr_torrentLoadResume (tor, ~0, ctor);
382  check ((loaded & TR_FR_FILENAMES) != 0);
383  check_streq (expected_files[0],                           files[0].name);
384  check_streq ("Felidae/Felinae/Felis/placeholder/Kyphi",   files[1].name);
385  check_streq ("Felidae/Felinae/Felis/placeholder/Saffron", files[2].name);
386  check_streq (expected_files[3],                           files[3].name);
387
388  /* ...and back again */
389  check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/placeholder", "catus"));
390  for (i=0; i<4; ++i)
391    {
392      check_streq (expected_files[i], files[i].name);
393      check (testFileExistsAndConsistsOfThisString (tor, 1, expected_contents[1]));
394    }
395  check (files[0].is_renamed == false);
396  check (files[1].is_renamed == true);
397  check (files[2].is_renamed == true);
398  check (files[3].is_renamed == false);
399
400  /***
401  ****  Test it an incomplete torrent...
402  ***/
403
404  /* remove the directory Felidae/Felinae/Felis/catus */
405  str = tr_buildPath (tor->currentDir, files[1].name, NULL);
406  remove (str);
407  tr_free (str);
408  str = tr_buildPath (tor->currentDir, files[2].name, NULL);
409  remove (str);
410  tmp = tr_dirname (str);
411  remove (tmp);
412  tr_free (tmp);
413  tr_free (str);
414  verify_and_block_until_done (tor);
415  testFileExistsAndConsistsOfThisString (tor, 0, expected_contents[0]);
416  check (tr_torrentFindFile (tor, 1) == NULL);
417  check (tr_torrentFindFile (tor, 2) == NULL);
418  testFileExistsAndConsistsOfThisString (tor, 3, expected_contents[3]);
419
420  /* rename a branch... */
421  check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/catus", "foo"));
422  check_streq (expected_files[0],                   files[0].name);
423  check_streq ("Felidae/Felinae/Felis/foo/Kyphi",   files[1].name);
424  check_streq ("Felidae/Felinae/Felis/foo/Saffron", files[2].name);
425  check_streq (expected_files[3],                   files[3].name);
426
427  /* ...and back again */
428  check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/foo", "catus"));
429  for (i=0; i<4; ++i)
430    check_streq (expected_files[i], files[i].name);
431
432  check_int_eq (0, torrentRenameAndWait (tor, "Felidae", "gabba"));
433  strings[0] = "gabba/Felinae/Acinonyx/Cheetah/Chester";
434  strings[1] = "gabba/Felinae/Felis/catus/Kyphi";
435  strings[2] = "gabba/Felinae/Felis/catus/Saffron";
436  strings[3] = "gabba/Pantherinae/Panthera/Tiger/Tony";
437  for (i=0; i<4; ++i)
438    {
439      check_streq (strings[i], files[i].name);
440      testFileExistsAndConsistsOfThisString (tor, i, expected_contents[i]);
441    }
442
443  /* rename the root, then a branch, and then a leaf... */
444  check_int_eq (0, torrentRenameAndWait (tor, "gabba", "Felidae"));
445  check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Pantherinae/Panthera/Tiger", "Snow Leopard"));
446  check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Pantherinae/Panthera/Snow Leopard/Tony", "10.6"));
447  strings[0] = "Felidae/Felinae/Acinonyx/Cheetah/Chester";
448  strings[1] = "Felidae/Felinae/Felis/catus/Kyphi";
449  strings[2] = "Felidae/Felinae/Felis/catus/Saffron";
450  strings[3] = "Felidae/Pantherinae/Panthera/Snow Leopard/10.6";
451  for (i=0; i<4; ++i)
452    {
453      check_streq (strings[i], files[i].name);
454      testFileExistsAndConsistsOfThisString (tor, i, expected_contents[i]);
455    }
456
457  /***
458  ****
459  ***/
460
461  /* cleanup */
462  tr_ctorFree (ctor);
463  tr_torrentRemove (tor, false, NULL);
464  return 0;
465}
466
467/***
468****
469***/
470
471static int
472test_partial_file (void)
473{
474  tr_file_index_t i;
475  tr_torrent * tor;
476  const tr_stat * st;
477  tr_file_stat * fst;
478  const uint32_t pieceCount = 33;
479  const uint32_t pieceSize = 32768;
480  const uint32_t length[] = { 1048576, 4096, 512 };
481  const uint64_t totalSize = length[0] + length[1] + length[2];
482  const char * strings[3];
483
484  /***
485  ****  create our test torrent with an incomplete .part file
486  ***/
487
488  tor = libtransmission_test_zero_torrent_init ();
489  check_int_eq (totalSize, tor->info.totalSize);
490  check_int_eq (pieceSize, tor->info.pieceSize);
491  check_int_eq (pieceCount, tor->info.pieceCount);
492  check_streq ("files-filled-with-zeroes/1048576", tor->info.files[0].name);
493  check_streq ("files-filled-with-zeroes/4096",    tor->info.files[1].name);
494  check_streq ("files-filled-with-zeroes/512",     tor->info.files[2].name);
495
496  libtransmission_test_zero_torrent_populate (tor, false);
497  fst = tr_torrentFiles (tor, NULL);
498  check_int_eq (length[0] - pieceSize, fst[0].bytesCompleted);
499  check_int_eq (length[1],             fst[1].bytesCompleted);
500  check_int_eq (length[2],             fst[2].bytesCompleted);
501  tr_torrentFilesFree (fst, tor->info.fileCount);
502  st = tr_torrentStat (tor);
503  check_int_eq (totalSize, st->sizeWhenDone);
504  check_int_eq (pieceSize, st->leftUntilDone);
505
506  /***
507  ****
508  ***/
509
510  check_int_eq (0, torrentRenameAndWait (tor, "files-filled-with-zeroes", "foo"));
511  check_int_eq (0, torrentRenameAndWait (tor, "foo/1048576", "bar"));
512  strings[0] = "foo/bar";
513  strings[1] = "foo/4096";
514  strings[2] = "foo/512";
515  for (i=0; i<3; ++i)
516    {
517      check_streq (strings[i], tor->info.files[i].name);
518    }
519
520  strings[0] = "foo/bar.part";
521  for (i=0; i<3; ++i)
522    {
523      char * expected = tr_buildPath (tor->currentDir, strings[i], NULL);
524      char * path = tr_torrentFindFile (tor, i);
525      check_streq (expected, path);
526      tr_free (path);
527      tr_free (expected);
528    }
529
530  tr_torrentRemove (tor, false, NULL);
531  return 0;
532}
533
534/***
535****
536***/
537
538int
539main (void)
540{
541  int ret;
542  const testFunc tests[] = { test_single_filename_torrent,
543                             test_multifile_torrent,
544                             test_partial_file };
545
546  libtransmission_test_session_init ();
547  ret = runTests (tests, NUM_TESTS (tests));
548  libtransmission_test_session_close ();
549
550  return ret;
551}
Note: See TracBrowser for help on using the repository browser.