test_project.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. # -*- coding: utf-8 -*-
  2. from __future__ import with_statement
  3. import unittest
  4. import contextlib
  5. import itertools
  6. try:
  7. import json
  8. except ImportError:
  9. import simplejson as json
  10. from mock import Mock, patch
  11. from txclib.project import Project
  12. from txclib.config import Flipdict
  13. class TestProject(unittest.TestCase):
  14. def test_extract_fields(self):
  15. """Test the functions that extract a field from a stats object."""
  16. stats = {
  17. 'completed': '80%',
  18. 'last_update': '00:00',
  19. 'foo': 'bar',
  20. }
  21. self.assertEqual(
  22. stats['completed'], '%s%%' % Project._extract_completed(stats)
  23. )
  24. self.assertEqual(stats['last_update'], Project._extract_updated(stats))
  25. def test_specifying_resources(self):
  26. """Test the various ways to specify resources in a project."""
  27. p = Project(init=False)
  28. resources = [
  29. 'proj1.res1',
  30. 'proj2.res2',
  31. 'transifex.txn',
  32. 'transifex.txo',
  33. ]
  34. with patch.object(p, 'get_resource_list') as mock:
  35. mock.return_value = resources
  36. cmd_args = [
  37. 'proj1.res1', '*1*', 'transifex*', '*r*',
  38. '*o', 'transifex.tx?', 'transifex.txn',
  39. ]
  40. results = [
  41. ['proj1.res1', ],
  42. ['proj1.res1', ],
  43. ['transifex.txn', 'transifex.txo', ],
  44. ['proj1.res1', 'proj2.res2', 'transifex.txn', 'transifex.txo', ],
  45. ['transifex.txo', ],
  46. ['transifex.txn', 'transifex.txo', ],
  47. ['transifex.txn', ],
  48. [],
  49. ]
  50. for i, arg in enumerate(cmd_args):
  51. resources = [arg]
  52. self.assertEqual(p.get_chosen_resources(resources), results[i])
  53. # wrong argument
  54. resources = ['*trasnifex*', ]
  55. self.assertRaises(Exception, p.get_chosen_resources, resources)
  56. class TestProjectMinimumPercent(unittest.TestCase):
  57. """Test the minimum-perc option."""
  58. def setUp(self):
  59. super(TestProjectMinimumPercent, self).setUp()
  60. self.p = Project(init=False)
  61. self.p.minimum_perc = None
  62. self.p.resource = "resource"
  63. def test_cmd_option(self):
  64. """Test command-line option."""
  65. self.p.minimum_perc = 20
  66. results = itertools.cycle([80, 90 ])
  67. def side_effect(*args):
  68. return results.next()
  69. with patch.object(self.p, "get_resource_option") as mock:
  70. mock.side_effect = side_effect
  71. self.assertFalse(self.p._satisfies_min_translated({'completed': '12%'}))
  72. self.assertTrue(self.p._satisfies_min_translated({'completed': '20%'}))
  73. self.assertTrue(self.p._satisfies_min_translated({'completed': '30%'}))
  74. def test_global_only(self):
  75. """Test only global option."""
  76. results = itertools.cycle([80, None ])
  77. def side_effect(*args):
  78. return results.next()
  79. with patch.object(self.p, "get_resource_option") as mock:
  80. mock.side_effect = side_effect
  81. self.assertFalse(self.p._satisfies_min_translated({'completed': '70%'}))
  82. self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'}))
  83. self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
  84. def test_local_lower_than_global(self):
  85. """Test the case where the local option is lower than the global."""
  86. results = itertools.cycle([80, 70 ])
  87. def side_effect(*args):
  88. return results.next()
  89. with patch.object(self.p, "get_resource_option") as mock:
  90. mock.side_effect = side_effect
  91. self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'}))
  92. self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'}))
  93. self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'}))
  94. self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
  95. def test_local_higher_than_global(self):
  96. """Test the case where the local option is lower than the global."""
  97. results = itertools.cycle([60, 70 ])
  98. def side_effect(*args):
  99. return results.next()
  100. with patch.object(self.p, "get_resource_option") as mock:
  101. mock.side_effect = side_effect
  102. self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'}))
  103. self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'}))
  104. self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'}))
  105. self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
  106. def test_local_only(self):
  107. """Test the case where the local option is lower than the global."""
  108. results = itertools.cycle([None, 70 ])
  109. def side_effect(*args):
  110. return results.next()
  111. with patch.object(self.p, "get_resource_option") as mock:
  112. mock.side_effect = side_effect
  113. self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'}))
  114. self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'}))
  115. self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'}))
  116. self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
  117. def test_no_option(self):
  118. """"Test the case there is nothing defined."""
  119. results = itertools.cycle([None, None ])
  120. def side_effect(*args):
  121. return results.next()
  122. with patch.object(self.p, "get_resource_option") as mock:
  123. mock.side_effect = side_effect
  124. self.assertTrue(self.p._satisfies_min_translated({'completed': '0%'}))
  125. self.assertTrue(self.p._satisfies_min_translated({'completed': '10%'}))
  126. self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'}))
  127. class TestProjectFilters(unittest.TestCase):
  128. """Test filters used to decide whether to push/pull a translation or not."""
  129. def setUp(self):
  130. super(TestProjectFilters, self).setUp()
  131. self.p = Project(init=False)
  132. self.p.minimum_perc = None
  133. self.p.resource = "resource"
  134. self.stats = {
  135. 'en': {
  136. 'completed': '100%', 'last_update': '2011-11-01 15:00:00',
  137. }, 'el': {
  138. 'completed': '60%', 'last_update': '2011-11-01 15:00:00',
  139. }, 'pt': {
  140. 'completed': '70%', 'last_update': '2011-11-01 15:00:00',
  141. },
  142. }
  143. self.langs = self.stats.keys()
  144. def test_add_translation(self):
  145. """Test filters for adding translations.
  146. We do not test here for minimum percentages.
  147. """
  148. with patch.object(self.p, "get_resource_option") as mock:
  149. mock.return_value = None
  150. should_add = self.p._should_add_translation
  151. for force in [True, False]:
  152. for lang in self.langs:
  153. self.assertTrue(should_add(lang, self.stats, force))
  154. # unknown language
  155. self.assertFalse(should_add('es', self.stats))
  156. def test_update_translation(self):
  157. """Test filters for updating a translation.
  158. We do not test here for minimum percentages.
  159. """
  160. with patch.object(self.p, "get_resource_option") as mock:
  161. mock.return_value = None
  162. should_update = self.p._should_update_translation
  163. force = True
  164. for lang in self.langs:
  165. self.assertTrue(should_update(lang, self.stats, 'foo', force))
  166. force = False # reminder
  167. local_file = 'foo'
  168. # unknown language
  169. self.assertFalse(should_update('es', self.stats, local_file))
  170. # no local file
  171. with patch.object(self.p, "_get_time_of_local_file") as time_mock:
  172. time_mock.return_value = None
  173. with patch.object(self.p, "get_full_path") as path_mock:
  174. path_mock.return_value = "foo"
  175. for lang in self.langs:
  176. self.assertTrue(
  177. should_update(lang, self.stats, local_file)
  178. )
  179. # older local files
  180. local_times = [self.p._generate_timestamp('2011-11-01 14:00:59')]
  181. results = itertools.cycle(local_times)
  182. def side_effect(*args):
  183. return results.next()
  184. with patch.object(self.p, "_get_time_of_local_file") as time_mock:
  185. time_mock.side_effect = side_effect
  186. with patch.object(self.p, "get_full_path") as path_mock:
  187. path_mock.return_value = "foo"
  188. for lang in self.langs:
  189. self.assertTrue(
  190. should_update(lang, self.stats, local_file)
  191. )
  192. # newer local files
  193. local_times = [self.p._generate_timestamp('2011-11-01 15:01:59')]
  194. results = itertools.cycle(local_times)
  195. def side_effect(*args):
  196. return results.next()
  197. with patch.object(self.p, "_get_time_of_local_file") as time_mock:
  198. time_mock.side_effect = side_effect
  199. with patch.object(self.p, "get_full_path") as path_mock:
  200. path_mock.return_value = "foo"
  201. for lang in self.langs:
  202. self.assertFalse(
  203. should_update(lang, self.stats, local_file)
  204. )
  205. def test_push_translation(self):
  206. """Test filters for pushing a translation file."""
  207. with patch.object(self.p, "get_resource_option") as mock:
  208. mock.return_value = None
  209. local_file = 'foo'
  210. should_push = self.p._should_push_translation
  211. force = True
  212. for lang in self.langs:
  213. self.assertTrue(should_push(lang, self.stats, local_file, force))
  214. force = False # reminder
  215. # unknown language
  216. self.assertTrue(should_push('es', self.stats, local_file))
  217. # older local files
  218. local_times = [self.p._generate_timestamp('2011-11-01 14:00:59')]
  219. results = itertools.cycle(local_times)
  220. def side_effect(*args):
  221. return results.next()
  222. with patch.object(self.p, "_get_time_of_local_file") as time_mock:
  223. time_mock.side_effect = side_effect
  224. with patch.object(self.p, "get_full_path") as path_mock:
  225. path_mock.return_value = "foo"
  226. for lang in self.langs:
  227. self.assertFalse(
  228. should_push(lang, self.stats, local_file)
  229. )
  230. # newer local files
  231. local_times = [self.p._generate_timestamp('2011-11-01 15:01:59')]
  232. results = itertools.cycle(local_times)
  233. def side_effect(*args):
  234. return results.next()
  235. with patch.object(self.p, "_get_time_of_local_file") as time_mock:
  236. time_mock.side_effect = side_effect
  237. with patch.object(self.p, "get_full_path") as path_mock:
  238. path_mock.return_value = "foo"
  239. for lang in self.langs:
  240. self.assertTrue(
  241. should_push(lang, self.stats, local_file)
  242. )
  243. class TestProjectPull(unittest.TestCase):
  244. """Test bits & pieces of the pull method."""
  245. def setUp(self):
  246. super(TestProjectPull, self).setUp()
  247. self.p = Project(init=False)
  248. self.p.minimum_perc = None
  249. self.p.resource = "resource"
  250. self.p.host = 'foo'
  251. self.p.project_slug = 'foo'
  252. self.p.resource_slug = 'foo'
  253. self.stats = {
  254. 'en': {
  255. 'completed': '100%', 'last_update': '2011-11-01 15:00:00',
  256. }, 'el': {
  257. 'completed': '60%', 'last_update': '2011-11-01 15:00:00',
  258. }, 'pt': {
  259. 'completed': '70%', 'last_update': '2011-11-01 15:00:00',
  260. },
  261. }
  262. self.langs = self.stats.keys()
  263. self.files = dict(zip(self.langs, itertools.repeat(None)))
  264. self.details = {'available_languages': []}
  265. for lang in self.langs:
  266. self.details['available_languages'].append({'code': lang})
  267. self.slang = 'en'
  268. self.lang_map = Flipdict()
  269. def test_new_translations(self):
  270. """Test finding new transaltions to add."""
  271. with patch.object(self.p, 'do_url_request') as resource_mock:
  272. resource_mock.return_value = json.dumps(self.details)
  273. files_keys = self.langs
  274. new_trans = self.p._new_translations_to_add
  275. for force in [True, False]:
  276. res = new_trans(
  277. self.files, self.slang, self.lang_map, self.stats, force
  278. )
  279. self.assertEquals(res, set([]))
  280. with patch.object(self.p, '_should_add_translation') as filter_mock:
  281. filter_mock.return_value = True
  282. for force in [True, False]:
  283. res = new_trans(
  284. {'el': None}, self.slang, self.lang_map, self.stats, force
  285. )
  286. self.assertEquals(res, set(['pt']))
  287. for force in [True, False]:
  288. res = new_trans(
  289. {}, self.slang, self.lang_map, self.stats, force
  290. )
  291. self.assertEquals(res, set(['el', 'pt']))
  292. files = {}
  293. files['pt_PT'] = None
  294. lang_map = {'pt': 'pt_PT'}
  295. for force in [True, False]:
  296. res = new_trans(
  297. files, self.slang, lang_map, self.stats, force
  298. )
  299. self.assertEquals(res, set(['el']))
  300. def test_languages_to_pull_empty_initial_list(self):
  301. """Test determining the languages to pull, when the initial
  302. list is empty.
  303. """
  304. languages = []
  305. force = False
  306. res = self.p._languages_to_pull(
  307. languages, self.files, self.lang_map, self.stats, force
  308. )
  309. existing = res[0]
  310. new = res[1]
  311. self.assertEquals(existing, set(['el', 'en', 'pt']))
  312. self.assertFalse(new)
  313. del self.files['el']
  314. self.files['el-gr'] = None
  315. self.lang_map['el'] = 'el-gr'
  316. res = self.p._languages_to_pull(
  317. languages, self.files, self.lang_map, self.stats, force
  318. )
  319. existing = res[0]
  320. new = res[1]
  321. self.assertEquals(existing, set(['el', 'en', 'pt']))
  322. self.assertFalse(new)
  323. def test_languages_to_pull_with_initial_list(self):
  324. """Test determining the languages to pull, then there is a
  325. language selection from the user.
  326. """
  327. languages = ['el', 'en']
  328. self.lang_map['el'] = 'el-gr'
  329. del self.files['el']
  330. self.files['el-gr'] = None
  331. force = False
  332. with patch.object(self.p, '_should_add_translation') as mock:
  333. mock.return_value = True
  334. res = self.p._languages_to_pull(
  335. languages, self.files, self.lang_map, self.stats, force
  336. )
  337. existing = res[0]
  338. new = res[1]
  339. self.assertEquals(existing, set(['en', 'el-gr', ]))
  340. self.assertFalse(new)
  341. mock.return_value = False
  342. res = self.p._languages_to_pull(
  343. languages, self.files, self.lang_map, self.stats, force
  344. )
  345. existing = res[0]
  346. new = res[1]
  347. self.assertEquals(existing, set(['en', 'el-gr', ]))
  348. self.assertFalse(new)
  349. del self.files['el-gr']
  350. mock.return_value = True
  351. res = self.p._languages_to_pull(
  352. languages, self.files, self.lang_map, self.stats, force
  353. )
  354. existing = res[0]
  355. new = res[1]
  356. self.assertEquals(existing, set(['en', ]))
  357. self.assertEquals(new, set(['el', ]))
  358. mock.return_value = False
  359. res = self.p._languages_to_pull(
  360. languages, self.files, self.lang_map, self.stats, force
  361. )
  362. existing = res[0]
  363. new = res[1]
  364. self.assertEquals(existing, set(['en', ]))
  365. self.assertEquals(new, set([]))
  366. def test_in_combination_with_force_option(self):
  367. """Test the minumum-perc option along with -f."""
  368. with patch.object(self.p, 'get_resource_option') as mock:
  369. mock.return_value = 70
  370. res = self.p._should_download('de', self.stats, None, False)
  371. self.assertEquals(res, False)
  372. res = self.p._should_download('el', self.stats, None, False)
  373. self.assertEquals(res, False)
  374. res = self.p._should_download('el', self.stats, None, True)
  375. self.assertEquals(res, False)
  376. res = self.p._should_download('en', self.stats, None, False)
  377. self.assertEquals(res, True)
  378. res = self.p._should_download('en', self.stats, None, True)
  379. self.assertEquals(res, True)
  380. with patch.object(self.p, '_remote_is_newer') as local_file_mock:
  381. local_file_mock = False
  382. res = self.p._should_download('pt', self.stats, None, False)
  383. self.assertEquals(res, True)
  384. res = self.p._should_download('pt', self.stats, None, True)
  385. self.assertEquals(res, True)
  386. class TestFormats(unittest.TestCase):
  387. """Tests for the supported formats."""
  388. def setUp(self):
  389. self.p = Project(init=False)
  390. def test_extensions(self):
  391. """Test returning the correct extension for a format."""
  392. sample_formats = {
  393. 'PO': {'file-extensions': '.po, .pot'},
  394. 'QT': {'file-extensions': '.ts'},
  395. }
  396. extensions = ['.po', '.ts', '', ]
  397. with patch.object(self.p, "do_url_request") as mock:
  398. mock.return_value = json.dumps(sample_formats)
  399. for (type_, ext) in zip(['PO', 'QT', 'NONE', ], extensions):
  400. extension = self.p._extension_for(type_)
  401. self.assertEquals(extension, ext)
  402. class TestOptions(unittest.TestCase):
  403. """Test the methods related to parsing the configuration file."""
  404. def setUp(self):
  405. self.p = Project(init=False)
  406. def test_get_option(self):
  407. """Test _get_option method."""
  408. with contextlib.nested(
  409. patch.object(self.p, 'get_resource_option'),
  410. patch.object(self.p, 'config', create=True)
  411. ) as (rmock, cmock):
  412. rmock.return_value = 'resource'
  413. cmock.has_option.return_value = 'main'
  414. cmock.get.return_value = 'main'
  415. self.assertEqual(self.p._get_option(None, None), 'resource')
  416. rmock.return_value = None
  417. cmock.has_option.return_value = 'main'
  418. cmock.get.return_value = 'main'
  419. self.assertEqual(self.p._get_option(None, None), 'main')
  420. cmock.has_option.return_value = None
  421. self.assertIs(self.p._get_option(None, None), None)
  422. class TestConfigurationOptions(unittest.TestCase):
  423. """Test the various configuration options."""
  424. def test_i18n_type(self):
  425. p = Project(init=False)
  426. type_string = 'type'
  427. i18n_type = 'PO'
  428. with patch.object(p, 'config', create=True) as config_mock:
  429. p.set_i18n_type([], i18n_type)
  430. calls = config_mock.method_calls
  431. self.assertEquals('set', calls[0][0])
  432. self.assertEquals('main', calls[0][1][0])
  433. p.set_i18n_type(['transifex.txo'], 'PO')
  434. calls = config_mock.method_calls
  435. self.assertEquals('set', calls[0][0])
  436. p.set_i18n_type(['transifex.txo', 'transifex.txn'], 'PO')
  437. calls = config_mock.method_calls
  438. self.assertEquals('set', calls[0][0])
  439. self.assertEquals('set', calls[1][0])
  440. class TestStats(unittest.TestCase):
  441. """Test the access to the stats objects."""
  442. def setUp(self):
  443. self.stats = Mock()
  444. self.stats.__getitem__ = Mock()
  445. self.stats.__getitem__.return_value = '12%'
  446. def test_field_used_per_mode(self):
  447. """Test the fields used for each mode."""
  448. Project._extract_completed(self.stats, 'translate')
  449. self.stats.__getitem__.assert_called_with('completed')
  450. Project._extract_completed(self.stats, 'reviewed')
  451. self.stats.__getitem__.assert_called_with('reviewed_percentage')