test_config.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. # coding: utf-8
  2. # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
  3. # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
  4. """Test the config file handling for coverage.py"""
  5. import sys
  6. import os
  7. import coverage
  8. from coverage.misc import CoverageException
  9. from tests.coveragetest import CoverageTest
  10. class ConfigTest(CoverageTest):
  11. """Tests of the different sources of configuration settings."""
  12. def test_default_config(self):
  13. # Just constructing a coverage() object gets the right defaults.
  14. cov = coverage.Coverage()
  15. self.assertFalse(cov.config.timid)
  16. self.assertFalse(cov.config.branch)
  17. self.assertEqual(cov.config.data_file, ".coverage")
  18. def test_arguments(self):
  19. # Arguments to the constructor are applied to the configuration.
  20. cov = coverage.Coverage(timid=True, data_file="fooey.dat", concurrency="multiprocessing")
  21. self.assertTrue(cov.config.timid)
  22. self.assertFalse(cov.config.branch)
  23. self.assertEqual(cov.config.data_file, "fooey.dat")
  24. self.assertEqual(cov.config.concurrency, ["multiprocessing"])
  25. def test_config_file(self):
  26. # A .coveragerc file will be read into the configuration.
  27. self.make_file(".coveragerc", """\
  28. # This is just a bogus .rc file for testing.
  29. [run]
  30. timid = True
  31. data_file = .hello_kitty.data
  32. """)
  33. cov = coverage.Coverage()
  34. self.assertTrue(cov.config.timid)
  35. self.assertFalse(cov.config.branch)
  36. self.assertEqual(cov.config.data_file, ".hello_kitty.data")
  37. def test_named_config_file(self):
  38. # You can name the config file what you like.
  39. self.make_file("my_cov.ini", """\
  40. [run]
  41. timid = True
  42. ; I wouldn't really use this as a data file...
  43. data_file = delete.me
  44. """)
  45. cov = coverage.Coverage(config_file="my_cov.ini")
  46. self.assertTrue(cov.config.timid)
  47. self.assertFalse(cov.config.branch)
  48. self.assertEqual(cov.config.data_file, "delete.me")
  49. def test_ignored_config_file(self):
  50. # You can disable reading the .coveragerc file.
  51. self.make_file(".coveragerc", """\
  52. [run]
  53. timid = True
  54. data_file = delete.me
  55. """)
  56. cov = coverage.Coverage(config_file=False)
  57. self.assertFalse(cov.config.timid)
  58. self.assertFalse(cov.config.branch)
  59. self.assertEqual(cov.config.data_file, ".coverage")
  60. def test_config_file_then_args(self):
  61. # The arguments override the .coveragerc file.
  62. self.make_file(".coveragerc", """\
  63. [run]
  64. timid = True
  65. data_file = weirdo.file
  66. """)
  67. cov = coverage.Coverage(timid=False, data_file=".mycov")
  68. self.assertFalse(cov.config.timid)
  69. self.assertFalse(cov.config.branch)
  70. self.assertEqual(cov.config.data_file, ".mycov")
  71. def test_data_file_from_environment(self):
  72. # There's an environment variable for the data_file.
  73. self.make_file(".coveragerc", """\
  74. [run]
  75. timid = True
  76. data_file = weirdo.file
  77. """)
  78. self.set_environ("COVERAGE_FILE", "fromenv.dat")
  79. cov = coverage.Coverage()
  80. self.assertEqual(cov.config.data_file, "fromenv.dat")
  81. # But the constructor arguments override the environment variable.
  82. cov = coverage.Coverage(data_file="fromarg.dat")
  83. self.assertEqual(cov.config.data_file, "fromarg.dat")
  84. def test_parse_errors(self):
  85. # Im-parsable values raise CoverageException, with details.
  86. bad_configs_and_msgs = [
  87. ("[run]\ntimid = maybe?\n", r"maybe[?]"),
  88. ("timid = 1\n", r"timid = 1"),
  89. ("[run\n", r"\[run"),
  90. ("[report]\nexclude_lines = foo(\n",
  91. r"Invalid \[report\].exclude_lines value 'foo\(': "
  92. r"(unbalanced parenthesis|missing \))"),
  93. ("[report]\npartial_branches = foo[\n",
  94. r"Invalid \[report\].partial_branches value 'foo\[': "
  95. r"(unexpected end of regular expression|unterminated character set)"),
  96. ("[report]\npartial_branches_always = foo***\n",
  97. r"Invalid \[report\].partial_branches_always value "
  98. r"'foo\*\*\*': "
  99. r"multiple repeat"),
  100. ]
  101. for bad_config, msg in bad_configs_and_msgs:
  102. print("Trying %r" % bad_config)
  103. self.make_file(".coveragerc", bad_config)
  104. with self.assertRaisesRegex(CoverageException, msg):
  105. coverage.Coverage()
  106. def test_environment_vars_in_config(self):
  107. # Config files can have $envvars in them.
  108. self.make_file(".coveragerc", """\
  109. [run]
  110. data_file = $DATA_FILE.fooey
  111. branch = $OKAY
  112. [report]
  113. exclude_lines =
  114. the_$$one
  115. another${THING}
  116. x${THING}y
  117. x${NOTHING}y
  118. huh$${X}what
  119. """)
  120. self.set_environ("DATA_FILE", "hello-world")
  121. self.set_environ("THING", "ZZZ")
  122. self.set_environ("OKAY", "yes")
  123. cov = coverage.Coverage()
  124. self.assertEqual(cov.config.data_file, "hello-world.fooey")
  125. self.assertEqual(cov.config.branch, True)
  126. self.assertEqual(
  127. cov.config.exclude_list,
  128. ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"]
  129. )
  130. def test_tweaks_after_constructor(self):
  131. # Arguments to the constructor are applied to the configuration.
  132. cov = coverage.Coverage(timid=True, data_file="fooey.dat")
  133. cov.set_option("run:timid", False)
  134. self.assertFalse(cov.config.timid)
  135. self.assertFalse(cov.config.branch)
  136. self.assertEqual(cov.config.data_file, "fooey.dat")
  137. self.assertFalse(cov.get_option("run:timid"))
  138. self.assertFalse(cov.get_option("run:branch"))
  139. self.assertEqual(cov.get_option("run:data_file"), "fooey.dat")
  140. def test_tweak_error_checking(self):
  141. # Trying to set an unknown config value raises an error.
  142. cov = coverage.Coverage()
  143. with self.assertRaises(CoverageException):
  144. cov.set_option("run:xyzzy", 12)
  145. with self.assertRaises(CoverageException):
  146. cov.set_option("xyzzy:foo", 12)
  147. with self.assertRaises(CoverageException):
  148. _ = cov.get_option("run:xyzzy")
  149. with self.assertRaises(CoverageException):
  150. _ = cov.get_option("xyzzy:foo")
  151. def test_tweak_plugin_options(self):
  152. # Plugin options have a more flexible syntax.
  153. cov = coverage.Coverage()
  154. cov.set_option("run:plugins", ["fooey.plugin", "xyzzy.coverage.plugin"])
  155. cov.set_option("fooey.plugin:xyzzy", 17)
  156. cov.set_option("xyzzy.coverage.plugin:plugh", ["a", "b"])
  157. with self.assertRaises(CoverageException):
  158. cov.set_option("no_such.plugin:foo", 23)
  159. self.assertEqual(cov.get_option("fooey.plugin:xyzzy"), 17)
  160. self.assertEqual(cov.get_option("xyzzy.coverage.plugin:plugh"), ["a", "b"])
  161. with self.assertRaises(CoverageException):
  162. _ = cov.get_option("no_such.plugin:foo")
  163. def test_unknown_option(self):
  164. self.make_file(".coveragerc", """\
  165. [run]
  166. xyzzy = 17
  167. """)
  168. msg = r"Unrecognized option '\[run\] xyzzy=' in config file .coveragerc"
  169. with self.assertRaisesRegex(CoverageException, msg):
  170. _ = coverage.Coverage()
  171. def test_misplaced_option(self):
  172. self.make_file(".coveragerc", """\
  173. [report]
  174. branch = True
  175. """)
  176. msg = r"Unrecognized option '\[report\] branch=' in config file .coveragerc"
  177. with self.assertRaisesRegex(CoverageException, msg):
  178. _ = coverage.Coverage()
  179. def test_unknown_option_in_other_ini_file(self):
  180. self.make_file("setup.cfg", """\
  181. [coverage:run]
  182. huh = what?
  183. """)
  184. msg = r"Unrecognized option '\[coverage:run\] huh=' in config file setup.cfg"
  185. with self.assertRaisesRegex(CoverageException, msg):
  186. _ = coverage.Coverage()
  187. class ConfigFileTest(CoverageTest):
  188. """Tests of the config file settings in particular."""
  189. def setUp(self):
  190. super(ConfigFileTest, self).setUp()
  191. # Parent class saves and restores sys.path, we can just modify it.
  192. # Add modules to the path so we can import plugins.
  193. sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules'))
  194. # This sample file tries to use lots of variation of syntax...
  195. # The {section} placeholder lets us nest these settings in another file.
  196. LOTSA_SETTINGS = """\
  197. # This is a settings file for coverage.py
  198. [{section}run]
  199. timid = yes
  200. data_file = something_or_other.dat
  201. branch = 1
  202. cover_pylib = TRUE
  203. parallel = on
  204. include = a/ , b/
  205. concurrency = thread
  206. source = myapp
  207. plugins =
  208. plugins.a_plugin
  209. plugins.another
  210. [{section}report]
  211. ; these settings affect reporting.
  212. exclude_lines =
  213. if 0:
  214. pragma:?\\s+no cover
  215. another_tab
  216. ignore_errors = TRUE
  217. omit =
  218. one, another, some_more,
  219. yet_more
  220. precision = 3
  221. partial_branches =
  222. pragma:?\\s+no branch
  223. partial_branches_always =
  224. if 0:
  225. while True:
  226. show_missing= TruE
  227. skip_covered = TruE
  228. [{section}html]
  229. directory = c:\\tricky\\dir.somewhere
  230. extra_css=something/extra.css
  231. title = Title & nums # nums!
  232. [{section}xml]
  233. output=mycov.xml
  234. package_depth = 17
  235. [{section}paths]
  236. source =
  237. .
  238. /home/ned/src/
  239. other = other, /home/ned/other, c:\\Ned\\etc
  240. [{section}plugins.a_plugin]
  241. hello = world
  242. ; comments still work.
  243. names = Jane/John/Jenny
  244. """
  245. # Just some sample setup.cfg text from the docs.
  246. SETUP_CFG = """\
  247. [bdist_rpm]
  248. release = 1
  249. packager = Jane Packager <janep@pysoft.com>
  250. doc_files = CHANGES.txt
  251. README.txt
  252. USAGE.txt
  253. doc/
  254. examples/
  255. """
  256. def assert_config_settings_are_correct(self, cov):
  257. """Check that `cov` has all the settings from LOTSA_SETTINGS."""
  258. self.assertTrue(cov.config.timid)
  259. self.assertEqual(cov.config.data_file, "something_or_other.dat")
  260. self.assertTrue(cov.config.branch)
  261. self.assertTrue(cov.config.cover_pylib)
  262. self.assertTrue(cov.config.parallel)
  263. self.assertEqual(cov.config.concurrency, ["thread"])
  264. self.assertEqual(cov.config.source, ["myapp"])
  265. self.assertEqual(cov.get_exclude_list(), ["if 0:", r"pragma:?\s+no cover", "another_tab"])
  266. self.assertTrue(cov.config.ignore_errors)
  267. self.assertEqual(cov.config.include, ["a/", "b/"])
  268. self.assertEqual(cov.config.omit, ["one", "another", "some_more", "yet_more"])
  269. self.assertEqual(cov.config.precision, 3)
  270. self.assertEqual(cov.config.partial_list, [r"pragma:?\s+no branch"])
  271. self.assertEqual(cov.config.partial_always_list, ["if 0:", "while True:"])
  272. self.assertEqual(cov.config.plugins, ["plugins.a_plugin", "plugins.another"])
  273. self.assertTrue(cov.config.show_missing)
  274. self.assertTrue(cov.config.skip_covered)
  275. self.assertEqual(cov.config.html_dir, r"c:\tricky\dir.somewhere")
  276. self.assertEqual(cov.config.extra_css, "something/extra.css")
  277. self.assertEqual(cov.config.html_title, "Title & nums # nums!")
  278. self.assertEqual(cov.config.xml_output, "mycov.xml")
  279. self.assertEqual(cov.config.xml_package_depth, 17)
  280. self.assertEqual(cov.config.paths, {
  281. 'source': ['.', '/home/ned/src/'],
  282. 'other': ['other', '/home/ned/other', 'c:\\Ned\\etc']
  283. })
  284. self.assertEqual(cov.config.get_plugin_options("plugins.a_plugin"), {
  285. 'hello': 'world',
  286. 'names': 'Jane/John/Jenny',
  287. })
  288. self.assertEqual(cov.config.get_plugin_options("plugins.another"), {})
  289. def test_config_file_settings(self):
  290. self.make_file(".coveragerc", self.LOTSA_SETTINGS.format(section=""))
  291. cov = coverage.Coverage()
  292. self.assert_config_settings_are_correct(cov)
  293. def test_config_file_settings_in_setupcfg(self):
  294. # Configuration will be read from setup.cfg from sections prefixed with
  295. # "coverage:"
  296. nested = self.LOTSA_SETTINGS.format(section="coverage:")
  297. self.make_file("setup.cfg", nested + "\n" + self.SETUP_CFG)
  298. cov = coverage.Coverage()
  299. self.assert_config_settings_are_correct(cov)
  300. def test_config_file_settings_in_setupcfg_if_coveragerc_specified(self):
  301. # Configuration will be read from setup.cfg from sections prefixed with
  302. # "coverage:", even if the API said to read from a (non-existent)
  303. # .coveragerc file.
  304. nested = self.LOTSA_SETTINGS.format(section="coverage:")
  305. self.make_file("setup.cfg", nested + "\n" + self.SETUP_CFG)
  306. cov = coverage.Coverage(config_file=".coveragerc")
  307. self.assert_config_settings_are_correct(cov)
  308. def test_setupcfg_only_if_not_coveragerc(self):
  309. self.make_file(".coveragerc", """\
  310. [run]
  311. include = foo
  312. """)
  313. self.make_file("setup.cfg", """\
  314. [coverage:run]
  315. omit = bar
  316. branch = true
  317. """)
  318. cov = coverage.Coverage()
  319. self.assertEqual(cov.config.include, ["foo"])
  320. self.assertEqual(cov.config.omit, None)
  321. self.assertEqual(cov.config.branch, False)
  322. def test_setupcfg_only_if_prefixed(self):
  323. self.make_file("setup.cfg", """\
  324. [run]
  325. omit = bar
  326. branch = true
  327. """)
  328. cov = coverage.Coverage()
  329. self.assertEqual(cov.config.omit, None)
  330. self.assertEqual(cov.config.branch, False)
  331. def test_non_ascii(self):
  332. self.make_file(".coveragerc", """\
  333. [report]
  334. exclude_lines =
  335. first
  336. ✘${TOX_ENVNAME}
  337. third
  338. [html]
  339. title = tabblo & «ταБЬℓσ» # numbers
  340. """)
  341. self.set_environ("TOX_ENVNAME", "weirdo")
  342. cov = coverage.Coverage()
  343. self.assertEqual(cov.config.exclude_list, ["first", "✘weirdo", "third"])
  344. self.assertEqual(cov.config.html_title, "tabblo & «ταБЬℓσ» # numbers")
  345. def test_unreadable_config(self):
  346. # If a config file is explicitly specified, then it is an error for it
  347. # to not be readable.
  348. bad_files = [
  349. "nosuchfile.txt",
  350. ".",
  351. ]
  352. for bad_file in bad_files:
  353. msg = "Couldn't read %r as a config file" % bad_file
  354. with self.assertRaisesRegex(CoverageException, msg):
  355. coverage.Coverage(config_file=bad_file)
  356. def test_nocoveragerc_file_when_specified(self):
  357. cov = coverage.Coverage(config_file=".coveragerc")
  358. self.assertFalse(cov.config.timid)
  359. self.assertFalse(cov.config.branch)
  360. self.assertEqual(cov.config.data_file, ".coverage")