utils.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. # ex:ts=4:sw=4:sts=4:et
  2. # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
  3. #
  4. # BitBake Tests for utils.py
  5. #
  6. # Copyright (C) 2012 Richard Purdie
  7. #
  8. # SPDX-License-Identifier: GPL-2.0-only
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License version 2 as
  12. # published by the Free Software Foundation.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License along
  20. # with this program; if not, write to the Free Software Foundation, Inc.,
  21. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  22. #
  23. import unittest
  24. import bb
  25. import os
  26. import tempfile
  27. import re
  28. class VerCmpString(unittest.TestCase):
  29. def test_vercmpstring(self):
  30. result = bb.utils.vercmp_string('1', '2')
  31. self.assertTrue(result < 0)
  32. result = bb.utils.vercmp_string('2', '1')
  33. self.assertTrue(result > 0)
  34. result = bb.utils.vercmp_string('1', '1.0')
  35. self.assertTrue(result < 0)
  36. result = bb.utils.vercmp_string('1', '1.1')
  37. self.assertTrue(result < 0)
  38. result = bb.utils.vercmp_string('1.1', '1_p2')
  39. self.assertTrue(result < 0)
  40. result = bb.utils.vercmp_string('1.0', '1.0+1.1-beta1')
  41. self.assertTrue(result < 0)
  42. result = bb.utils.vercmp_string('1.1', '1.0+1.1-beta1')
  43. self.assertTrue(result > 0)
  44. result = bb.utils.vercmp_string('1.', '1.1')
  45. self.assertTrue(result < 0)
  46. result = bb.utils.vercmp_string('1.1', '1.')
  47. self.assertTrue(result > 0)
  48. def test_explode_dep_versions(self):
  49. correctresult = {"foo" : ["= 1.10"]}
  50. result = bb.utils.explode_dep_versions2("foo (= 1.10)")
  51. self.assertEqual(result, correctresult)
  52. result = bb.utils.explode_dep_versions2("foo (=1.10)")
  53. self.assertEqual(result, correctresult)
  54. result = bb.utils.explode_dep_versions2("foo ( = 1.10)")
  55. self.assertEqual(result, correctresult)
  56. result = bb.utils.explode_dep_versions2("foo ( =1.10)")
  57. self.assertEqual(result, correctresult)
  58. result = bb.utils.explode_dep_versions2("foo ( = 1.10 )")
  59. self.assertEqual(result, correctresult)
  60. result = bb.utils.explode_dep_versions2("foo ( =1.10 )")
  61. self.assertEqual(result, correctresult)
  62. def test_vercmp_string_op(self):
  63. compareops = [('1', '1', '=', True),
  64. ('1', '1', '==', True),
  65. ('1', '1', '!=', False),
  66. ('1', '1', '>', False),
  67. ('1', '1', '<', False),
  68. ('1', '1', '>=', True),
  69. ('1', '1', '<=', True),
  70. ('1', '0', '=', False),
  71. ('1', '0', '==', False),
  72. ('1', '0', '!=', True),
  73. ('1', '0', '>', True),
  74. ('1', '0', '<', False),
  75. ('1', '0', '>>', True),
  76. ('1', '0', '<<', False),
  77. ('1', '0', '>=', True),
  78. ('1', '0', '<=', False),
  79. ('0', '1', '=', False),
  80. ('0', '1', '==', False),
  81. ('0', '1', '!=', True),
  82. ('0', '1', '>', False),
  83. ('0', '1', '<', True),
  84. ('0', '1', '>>', False),
  85. ('0', '1', '<<', True),
  86. ('0', '1', '>=', False),
  87. ('0', '1', '<=', True)]
  88. for arg1, arg2, op, correctresult in compareops:
  89. result = bb.utils.vercmp_string_op(arg1, arg2, op)
  90. self.assertEqual(result, correctresult, 'vercmp_string_op("%s", "%s", "%s") != %s' % (arg1, arg2, op, correctresult))
  91. # Check that clearly invalid operator raises an exception
  92. self.assertRaises(bb.utils.VersionStringException, bb.utils.vercmp_string_op, '0', '0', '$')
  93. class Path(unittest.TestCase):
  94. def test_unsafe_delete_path(self):
  95. checkitems = [('/', True),
  96. ('//', True),
  97. ('///', True),
  98. (os.getcwd().count(os.sep) * ('..' + os.sep), True),
  99. (os.environ.get('HOME', '/home/test'), True),
  100. ('/home/someone', True),
  101. ('/home/other/', True),
  102. ('/home/other/subdir', False),
  103. ('', False)]
  104. for arg1, correctresult in checkitems:
  105. result = bb.utils._check_unsafe_delete_path(arg1)
  106. self.assertEqual(result, correctresult, '_check_unsafe_delete_path("%s") != %s' % (arg1, correctresult))
  107. class EditMetadataFile(unittest.TestCase):
  108. _origfile = """
  109. # A comment
  110. HELLO = "oldvalue"
  111. THIS = "that"
  112. # Another comment
  113. NOCHANGE = "samevalue"
  114. OTHER = 'anothervalue'
  115. MULTILINE = "a1 \\
  116. a2 \\
  117. a3"
  118. MULTILINE2 := " \\
  119. b1 \\
  120. b2 \\
  121. b3 \\
  122. "
  123. MULTILINE3 = " \\
  124. c1 \\
  125. c2 \\
  126. c3 \\
  127. "
  128. do_functionname() {
  129. command1 ${VAL1} ${VAL2}
  130. command2 ${VAL3} ${VAL4}
  131. }
  132. """
  133. def _testeditfile(self, varvalues, compareto, dummyvars=None):
  134. if dummyvars is None:
  135. dummyvars = []
  136. with tempfile.NamedTemporaryFile('w', delete=False) as tf:
  137. tf.write(self._origfile)
  138. tf.close()
  139. try:
  140. varcalls = []
  141. def handle_file(varname, origvalue, op, newlines):
  142. self.assertIn(varname, varvalues, 'Callback called for variable %s not in the list!' % varname)
  143. self.assertNotIn(varname, dummyvars, 'Callback called for variable %s in dummy list!' % varname)
  144. varcalls.append(varname)
  145. return varvalues[varname]
  146. bb.utils.edit_metadata_file(tf.name, varvalues.keys(), handle_file)
  147. with open(tf.name) as f:
  148. modfile = f.readlines()
  149. # Ensure the output matches the expected output
  150. self.assertEqual(compareto.splitlines(True), modfile)
  151. # Ensure the callback function was called for every variable we asked for
  152. # (plus allow testing behaviour when a requested variable is not present)
  153. self.assertEqual(sorted(varvalues.keys()), sorted(varcalls + dummyvars))
  154. finally:
  155. os.remove(tf.name)
  156. def test_edit_metadata_file_nochange(self):
  157. # Test file doesn't get modified with nothing to do
  158. self._testeditfile({}, self._origfile)
  159. # Test file doesn't get modified with only dummy variables
  160. self._testeditfile({'DUMMY1': ('should_not_set', None, 0, True),
  161. 'DUMMY2': ('should_not_set_again', None, 0, True)}, self._origfile, dummyvars=['DUMMY1', 'DUMMY2'])
  162. # Test file doesn't get modified with some the same values
  163. self._testeditfile({'THIS': ('that', None, 0, True),
  164. 'OTHER': ('anothervalue', None, 0, True),
  165. 'MULTILINE3': (' c1 c2 c3 ', None, 4, False)}, self._origfile)
  166. def test_edit_metadata_file_1(self):
  167. newfile1 = """
  168. # A comment
  169. HELLO = "newvalue"
  170. THIS = "that"
  171. # Another comment
  172. NOCHANGE = "samevalue"
  173. OTHER = 'anothervalue'
  174. MULTILINE = "a1 \\
  175. a2 \\
  176. a3"
  177. MULTILINE2 := " \\
  178. b1 \\
  179. b2 \\
  180. b3 \\
  181. "
  182. MULTILINE3 = " \\
  183. c1 \\
  184. c2 \\
  185. c3 \\
  186. "
  187. do_functionname() {
  188. command1 ${VAL1} ${VAL2}
  189. command2 ${VAL3} ${VAL4}
  190. }
  191. """
  192. self._testeditfile({'HELLO': ('newvalue', None, 4, True)}, newfile1)
  193. def test_edit_metadata_file_2(self):
  194. newfile2 = """
  195. # A comment
  196. HELLO = "oldvalue"
  197. THIS = "that"
  198. # Another comment
  199. NOCHANGE = "samevalue"
  200. OTHER = 'anothervalue'
  201. MULTILINE = " \\
  202. d1 \\
  203. d2 \\
  204. d3 \\
  205. "
  206. MULTILINE2 := " \\
  207. b1 \\
  208. b2 \\
  209. b3 \\
  210. "
  211. MULTILINE3 = "nowsingle"
  212. do_functionname() {
  213. command1 ${VAL1} ${VAL2}
  214. command2 ${VAL3} ${VAL4}
  215. }
  216. """
  217. self._testeditfile({'MULTILINE': (['d1','d2','d3'], None, 4, False),
  218. 'MULTILINE3': ('nowsingle', None, 4, True),
  219. 'NOTPRESENT': (['a', 'b'], None, 4, False)}, newfile2, dummyvars=['NOTPRESENT'])
  220. def test_edit_metadata_file_3(self):
  221. newfile3 = """
  222. # A comment
  223. HELLO = "oldvalue"
  224. # Another comment
  225. NOCHANGE = "samevalue"
  226. OTHER = "yetanothervalue"
  227. MULTILINE = "e1 \\
  228. e2 \\
  229. e3 \\
  230. "
  231. MULTILINE2 := "f1 \\
  232. \tf2 \\
  233. \t"
  234. MULTILINE3 = " \\
  235. c1 \\
  236. c2 \\
  237. c3 \\
  238. "
  239. do_functionname() {
  240. othercommand_one a b c
  241. othercommand_two d e f
  242. }
  243. """
  244. self._testeditfile({'do_functionname()': (['othercommand_one a b c', 'othercommand_two d e f'], None, 4, False),
  245. 'MULTILINE2': (['f1', 'f2'], None, '\t', True),
  246. 'MULTILINE': (['e1', 'e2', 'e3'], None, -1, True),
  247. 'THIS': (None, None, 0, False),
  248. 'OTHER': ('yetanothervalue', None, 0, True)}, newfile3)
  249. def test_edit_metadata_file_4(self):
  250. newfile4 = """
  251. # A comment
  252. HELLO = "oldvalue"
  253. THIS = "that"
  254. # Another comment
  255. OTHER = 'anothervalue'
  256. MULTILINE = "a1 \\
  257. a2 \\
  258. a3"
  259. MULTILINE2 := " \\
  260. b1 \\
  261. b2 \\
  262. b3 \\
  263. "
  264. """
  265. self._testeditfile({'NOCHANGE': (None, None, 0, False),
  266. 'MULTILINE3': (None, None, 0, False),
  267. 'THIS': ('that', None, 0, False),
  268. 'do_functionname()': (None, None, 0, False)}, newfile4)
  269. def test_edit_metadata(self):
  270. newfile5 = """
  271. # A comment
  272. HELLO = "hithere"
  273. # A new comment
  274. THIS += "that"
  275. # Another comment
  276. NOCHANGE = "samevalue"
  277. OTHER = 'anothervalue'
  278. MULTILINE = "a1 \\
  279. a2 \\
  280. a3"
  281. MULTILINE2 := " \\
  282. b1 \\
  283. b2 \\
  284. b3 \\
  285. "
  286. MULTILINE3 = " \\
  287. c1 \\
  288. c2 \\
  289. c3 \\
  290. "
  291. NEWVAR = "value"
  292. do_functionname() {
  293. command1 ${VAL1} ${VAL2}
  294. command2 ${VAL3} ${VAL4}
  295. }
  296. """
  297. def handle_var(varname, origvalue, op, newlines):
  298. if varname == 'THIS':
  299. newlines.append('# A new comment\n')
  300. elif varname == 'do_functionname()':
  301. newlines.append('NEWVAR = "value"\n')
  302. newlines.append('\n')
  303. valueitem = varvalues.get(varname, None)
  304. if valueitem:
  305. return valueitem
  306. else:
  307. return (origvalue, op, 0, True)
  308. varvalues = {'HELLO': ('hithere', None, 0, True), 'THIS': ('that', '+=', 0, True)}
  309. varlist = ['HELLO', 'THIS', 'do_functionname()']
  310. (updated, newlines) = bb.utils.edit_metadata(self._origfile.splitlines(True), varlist, handle_var)
  311. self.assertTrue(updated, 'List should be updated but isn\'t')
  312. self.assertEqual(newlines, newfile5.splitlines(True))
  313. # Make sure the orig value matches what we expect it to be
  314. def test_edit_metadata_origvalue(self):
  315. origfile = """
  316. MULTILINE = " stuff \\
  317. morestuff"
  318. """
  319. expected_value = "stuff morestuff"
  320. global value_in_callback
  321. value_in_callback = ""
  322. def handle_var(varname, origvalue, op, newlines):
  323. global value_in_callback
  324. value_in_callback = origvalue
  325. return (origvalue, op, -1, False)
  326. bb.utils.edit_metadata(origfile.splitlines(True),
  327. ['MULTILINE'],
  328. handle_var)
  329. testvalue = re.sub('\s+', ' ', value_in_callback.strip())
  330. self.assertEqual(expected_value, testvalue)
  331. class EditBbLayersConf(unittest.TestCase):
  332. def _test_bblayers_edit(self, before, after, add, remove, notadded, notremoved):
  333. with tempfile.NamedTemporaryFile('w', delete=False) as tf:
  334. tf.write(before)
  335. tf.close()
  336. try:
  337. actual_notadded, actual_notremoved = bb.utils.edit_bblayers_conf(tf.name, add, remove)
  338. with open(tf.name) as f:
  339. actual_after = f.readlines()
  340. self.assertEqual(after.splitlines(True), actual_after)
  341. self.assertEqual(notadded, actual_notadded)
  342. self.assertEqual(notremoved, actual_notremoved)
  343. finally:
  344. os.remove(tf.name)
  345. def test_bblayers_remove(self):
  346. before = r"""
  347. # A comment
  348. BBPATH = "${TOPDIR}"
  349. BBFILES ?= ""
  350. BBLAYERS = " \
  351. /home/user/path/layer1 \
  352. /home/user/path/layer2 \
  353. /home/user/path/subpath/layer3 \
  354. /home/user/path/layer4 \
  355. "
  356. """
  357. after = r"""
  358. # A comment
  359. BBPATH = "${TOPDIR}"
  360. BBFILES ?= ""
  361. BBLAYERS = " \
  362. /home/user/path/layer1 \
  363. /home/user/path/subpath/layer3 \
  364. /home/user/path/layer4 \
  365. "
  366. """
  367. self._test_bblayers_edit(before, after,
  368. None,
  369. '/home/user/path/layer2',
  370. [],
  371. [])
  372. def test_bblayers_add(self):
  373. before = r"""
  374. # A comment
  375. BBPATH = "${TOPDIR}"
  376. BBFILES ?= ""
  377. BBLAYERS = " \
  378. /home/user/path/layer1 \
  379. /home/user/path/layer2 \
  380. /home/user/path/subpath/layer3 \
  381. /home/user/path/layer4 \
  382. "
  383. """
  384. after = r"""
  385. # A comment
  386. BBPATH = "${TOPDIR}"
  387. BBFILES ?= ""
  388. BBLAYERS = " \
  389. /home/user/path/layer1 \
  390. /home/user/path/layer2 \
  391. /home/user/path/subpath/layer3 \
  392. /home/user/path/layer4 \
  393. /other/path/to/layer5 \
  394. "
  395. """
  396. self._test_bblayers_edit(before, after,
  397. '/other/path/to/layer5/',
  398. None,
  399. [],
  400. [])
  401. def test_bblayers_add_remove(self):
  402. before = r"""
  403. # A comment
  404. BBPATH = "${TOPDIR}"
  405. BBFILES ?= ""
  406. BBLAYERS = " \
  407. /home/user/path/layer1 \
  408. /home/user/path/layer2 \
  409. /home/user/path/subpath/layer3 \
  410. /home/user/path/layer4 \
  411. "
  412. """
  413. after = r"""
  414. # A comment
  415. BBPATH = "${TOPDIR}"
  416. BBFILES ?= ""
  417. BBLAYERS = " \
  418. /home/user/path/layer1 \
  419. /home/user/path/layer2 \
  420. /home/user/path/layer4 \
  421. /other/path/to/layer5 \
  422. "
  423. """
  424. self._test_bblayers_edit(before, after,
  425. ['/other/path/to/layer5', '/home/user/path/layer2/'], '/home/user/path/subpath/layer3/',
  426. ['/home/user/path/layer2'],
  427. [])
  428. def test_bblayers_add_remove_home(self):
  429. before = r"""
  430. # A comment
  431. BBPATH = "${TOPDIR}"
  432. BBFILES ?= ""
  433. BBLAYERS = " \
  434. ~/path/layer1 \
  435. ~/path/layer2 \
  436. ~/otherpath/layer3 \
  437. ~/path/layer4 \
  438. "
  439. """
  440. after = r"""
  441. # A comment
  442. BBPATH = "${TOPDIR}"
  443. BBFILES ?= ""
  444. BBLAYERS = " \
  445. ~/path/layer2 \
  446. ~/path/layer4 \
  447. ~/path2/layer5 \
  448. "
  449. """
  450. self._test_bblayers_edit(before, after,
  451. [os.environ['HOME'] + '/path/layer4', '~/path2/layer5'],
  452. [os.environ['HOME'] + '/otherpath/layer3', '~/path/layer1', '~/path/notinlist'],
  453. [os.environ['HOME'] + '/path/layer4'],
  454. ['~/path/notinlist'])
  455. def test_bblayers_add_remove_plusequals(self):
  456. before = r"""
  457. # A comment
  458. BBPATH = "${TOPDIR}"
  459. BBFILES ?= ""
  460. BBLAYERS += " \
  461. /home/user/path/layer1 \
  462. /home/user/path/layer2 \
  463. "
  464. """
  465. after = r"""
  466. # A comment
  467. BBPATH = "${TOPDIR}"
  468. BBFILES ?= ""
  469. BBLAYERS += " \
  470. /home/user/path/layer2 \
  471. /home/user/path/layer3 \
  472. "
  473. """
  474. self._test_bblayers_edit(before, after,
  475. '/home/user/path/layer3',
  476. '/home/user/path/layer1',
  477. [],
  478. [])
  479. def test_bblayers_add_remove_plusequals2(self):
  480. before = r"""
  481. # A comment
  482. BBPATH = "${TOPDIR}"
  483. BBFILES ?= ""
  484. BBLAYERS += " \
  485. /home/user/path/layer1 \
  486. /home/user/path/layer2 \
  487. /home/user/path/layer3 \
  488. "
  489. BBLAYERS += "/home/user/path/layer4"
  490. BBLAYERS += "/home/user/path/layer5"
  491. """
  492. after = r"""
  493. # A comment
  494. BBPATH = "${TOPDIR}"
  495. BBFILES ?= ""
  496. BBLAYERS += " \
  497. /home/user/path/layer2 \
  498. /home/user/path/layer3 \
  499. "
  500. BBLAYERS += "/home/user/path/layer5"
  501. BBLAYERS += "/home/user/otherpath/layer6"
  502. """
  503. self._test_bblayers_edit(before, after,
  504. ['/home/user/otherpath/layer6', '/home/user/path/layer3'], ['/home/user/path/layer1', '/home/user/path/layer4', '/home/user/path/layer7'],
  505. ['/home/user/path/layer3'],
  506. ['/home/user/path/layer7'])