integration.rst 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. .. _integration-tests:
  2. =================
  3. Integration Tests
  4. =================
  5. The Salt integration tests come with a number of classes and methods which
  6. allow for components to be easily tested. These classes are generally inherited
  7. from and provide specific methods for hooking into the running integration test
  8. environment created by the integration tests.
  9. It is noteworthy that since integration tests validate against a running
  10. environment that they are generally the preferred means to write tests.
  11. The integration system is all located under ``tests/integration`` in the Salt
  12. source tree. Each directory within ``tests/integration`` corresponds to a
  13. directory in Salt's tree structure. For example, the integration tests for the
  14. ``test.py`` Salt module that is located in ``salt/modules`` should also be
  15. named ``test.py`` and reside in ``tests/integration/modules``.
  16. Preparing to Write Integration Tests
  17. ====================================
  18. This guide assumes that your Salt development environment is already configured
  19. and that you have a basic understanding of contributing to the Salt codebase.
  20. If you're unfamiliar with either of these topics, please refer to the
  21. :ref:`Installing Salt for Development<installing-for-development>` and the
  22. :ref:`Contributing<contributing>` pages, respectively.
  23. This documentation also assumes that you have an understanding of how to
  24. :ref:`run Salt's test suite<running-the-tests>`, including running the
  25. :ref:`test subsections<running-test-subsections>`, and running a single
  26. integration test file, class, or individual test.
  27. Best Practices
  28. ==============
  29. Integration tests should be written to the following specifications.
  30. What to Test?
  31. -------------
  32. Since integration tests are used to validate against a running Salt environment,
  33. integration tests should be written with the Salt components, and their various
  34. interactions, in mind.
  35. - Isolate testing functionality. Don't rely on the pass or failure of other,
  36. separate tests.
  37. - Individual tests should test against a single behavior.
  38. - Since it occasionally takes some effort to "set up" an individual test, it may
  39. be necessary to call several functions within a single test. However, be sure
  40. that once the work has been done to set up a test, make sure you are clear
  41. about the functionality that is being tested.
  42. Naming Conventions
  43. ------------------
  44. Test names and docstrings should indicate what functionality is being tested.
  45. Test functions are named ``test_<fcn>_<test-name>`` where ``<fcn>`` is the
  46. function being tested and ``<test-name>`` describes the behavior being tested.
  47. In order for integration tests to get picked up during a run of the test suite,
  48. each individual test must be prepended with the ``test_`` naming syntax, as
  49. described above.
  50. If a function does not start with ``test_``, then the function acts as a "normal"
  51. function and is not considered a testing function. It will not be included in the
  52. test run or testing output.
  53. The setUp and tearDown Functions
  54. --------------------------------
  55. There are two special functions that can be utilized in the integration side of
  56. Salt's test suite: ``setUp`` and ``tearDown``. While these functions are not
  57. required in all test files, there are many examples in Salt's integration
  58. test suite illustrating the broad usefulness of each function.
  59. The ``setUp`` function is used to set up any repetitive or useful tasks that the
  60. tests in a test class need before running. For example, any of the ``mac_*``
  61. integration tests should only run on macOS machines. The ``setUp`` function can
  62. be used to test for the presence of the ``Darwin`` kernel. If the ``Darwin``
  63. kernel is not present, then the test should be skipped.
  64. .. code-block:: python
  65. def setUp(self):
  66. """
  67. Sets up test requirements
  68. """
  69. os_grain = self.run_function("grains.item", ["kernel"])
  70. if os_grain["kernel"] not in "Darwin":
  71. self.skipTest("Test not applicable to '{kernel}' kernel".format(**os_grain))
  72. The ``setUp`` function can be used for many things. The above code snippet is
  73. only one example. Another example might be to ensure that a particular setting
  74. is present before running tests that would require the setting.
  75. The ``tearDown`` function is used to clean up after any tests. This function is
  76. useful for restoring any settings that might have been changed during the test
  77. run.
  78. .. note::
  79. The ``setUp`` and ``tearDown`` functions run before and after each test
  80. in the test class that the ``setUp`` and ``tearDown`` functions are defined.
  81. Be sure to read the `Destructive vs Non-Destructive Tests`_ section when
  82. using any kind of destructive functions that might alter the system running the
  83. test suite in either the ``setUp`` or ``tearDown`` function definitions.
  84. Testing Order
  85. -------------
  86. The test functions within a test class do not run in the order they were defined,
  87. but instead run in lexicographical order.
  88. Note that if any ``setUp`` or ``tearDown`` functions are defined in the class,
  89. those functions will run before (for ``setUp``) or after (for ``tearDown``) each
  90. test case.
  91. Integration Classes
  92. ===================
  93. The integration classes are located in ``tests/integration/__init__.py`` and
  94. can be extended therein. There are four classes available to extend:
  95. * `ModuleCase`_
  96. * `ShellCase`_
  97. * `SSHCase`_
  98. * `SyndicCase`_
  99. ModuleCase
  100. ----------
  101. Used to define executions run via the master to minions and to call
  102. single modules and states. The available testing functions are:
  103. run_function
  104. ~~~~~~~~~~~~
  105. Run a single salt function and condition the return down to match the
  106. behavior of the raw function call. This will run the command and only
  107. return the results from a single minion to verify.
  108. run_state
  109. ~~~~~~~~~
  110. Run the state.single command and return the state return structure.
  111. minion_run
  112. ~~~~~~~~~~
  113. Run a single salt function on the 'minion' target and condition the
  114. return down to match the behavior of the raw function call.
  115. ShellCase
  116. ---------
  117. Shell out to the scripts which ship with Salt. The testing functions are:
  118. run_cp
  119. ~~~~~~
  120. Execute salt-cp. Pass in the argument string as it would be
  121. passed on the command line.
  122. run_call
  123. ~~~~~~~~
  124. Execute salt-call, pass in the argument string as it would be
  125. passed on the command line.
  126. run_cloud
  127. ~~~~~~~~~
  128. Execute the salt-cloud command. Pass in the argument string as
  129. it would be passed on the command line.
  130. run_key
  131. ~~~~~~~
  132. Execute the salt-key command. Pass in the argument string as it
  133. would be passed on the command line.
  134. run_run
  135. ~~~~~~~
  136. Execute the salt-run command. Pass in the argument string as it
  137. would be passed on the command line.
  138. run_run_plus
  139. ~~~~~~~~~~~~
  140. Execute the runner function the and return the return data and output in a dict
  141. run_salt
  142. ~~~~~~~~
  143. Execute the salt command. Pass in the argument string as it would be
  144. passed on the command line.
  145. run_script
  146. ~~~~~~~~~~
  147. Execute a salt script with the given argument string.
  148. run_ssh
  149. ~~~~~~~
  150. Execute the salt-ssh. Pass in the argument string as it would be
  151. passed on the command line.
  152. SSHCase
  153. -------
  154. Used to execute remote commands via salt-ssh. The available methods are
  155. as follows:
  156. run_function
  157. ~~~~~~~~~~~~
  158. Run a single salt function via salt-ssh and condition the return down to
  159. match the behavior of the raw function call. This will run the command
  160. and only return the results from a single minion to verify.
  161. SyndicCase
  162. ----------
  163. Used to execute remote commands via a syndic and is only used to verify
  164. the capabilities of the Salt Syndic. The available methods are as follows:
  165. run_function
  166. ~~~~~~~~~~~~
  167. Run a single salt function and condition the return down to match the
  168. behavior of the raw function call. This will run the command and only
  169. return the results from a single minion to verify.
  170. .. _integration-class-examples:
  171. Examples
  172. ========
  173. The following sections define simple integration tests present in Salt's
  174. integration test suite for each type of testing class.
  175. Module Example via ModuleCase Class
  176. -----------------------------------
  177. Import the integration module, this module is already added to the python path
  178. by the test execution. Inherit from the ``integration.ModuleCase`` class.
  179. Now the workhorse method ``run_function`` can be used to test a module:
  180. .. code-block:: python
  181. import os
  182. from tests.support.case import ModuleCase
  183. class TestModuleTest(ModuleCase):
  184. """
  185. Validate the test module
  186. """
  187. def test_ping(self):
  188. """
  189. test.ping
  190. """
  191. self.assertTrue(self.run_function("test.ping"))
  192. def test_echo(self):
  193. """
  194. test.echo
  195. """
  196. self.assertEqual(self.run_function("test.echo", ["text"]), "text")
  197. The fist example illustrates the testing master issuing a ``test.ping`` call
  198. to a testing minion. The test asserts that the minion returned with a ``True``
  199. value to the master from the ``test.ping`` call.
  200. The second example similarly verifies that the minion executed the
  201. ``test.echo`` command with the ``text`` argument. The ``assertEqual`` call
  202. maintains that the minion ran the function and returned the data as expected
  203. to the master.
  204. Shell Example via ShellCase
  205. ---------------------------
  206. Validating the shell commands can be done via shell tests:
  207. .. code-block:: python
  208. import sys
  209. import shutil
  210. import tempfile
  211. from tests.support.case import ShellCase
  212. class KeyTest(ShellCase):
  213. """
  214. Test salt-key script
  215. """
  216. _call_binary_ = "salt-key"
  217. def test_list(self):
  218. """
  219. test salt-key -L
  220. """
  221. data = self.run_key("-L")
  222. expect = [
  223. "Unaccepted Keys:",
  224. "Accepted Keys:",
  225. "minion",
  226. "sub_minion",
  227. "Rejected:",
  228. "",
  229. ]
  230. self.assertEqual(data, expect)
  231. This example verifies that the ``salt-key`` command executes and returns as
  232. expected by making use of the ``run_key`` method.
  233. SSH Example via SSHCase
  234. -----------------------
  235. Testing salt-ssh functionality can be done using the SSHCase test class:
  236. .. code-block:: python
  237. from tests.support.case import SSHCase
  238. class SSHGrainsTest(SSHCase):
  239. """
  240. Test salt-ssh grains functionality
  241. Depend on proper environment set by integration.SSHCase class
  242. """
  243. def test_grains_id(self):
  244. """
  245. Test salt-ssh grains id work for localhost.
  246. """
  247. cmd = self.run_function("grains.get", ["id"])
  248. self.assertEqual(cmd, "localhost")
  249. Testing Event System via SaltMinionEventAssertsMixin
  250. ----------------------------------------------------
  251. The fundamentially asynchronous nature of Salt makes testing the event system a challenge.
  252. The ``SaltMinionEventAssertsMixin`` provides a facility for testing that events were received
  253. on a minion event bus.
  254. .. code-block:: python
  255. import salt.utils.event
  256. from tests.support.mixins import SaltEventAssertsMixin
  257. class TestEvent(SaltEventAssertsMixin):
  258. """
  259. Example test of firing an event and receiving it
  260. """
  261. def test_event(self):
  262. e = salt.utils.event.get_event(
  263. "minion", sock_dir=self.minion_opts["sock_dir"], opts=self.minion_opts
  264. )
  265. e.fire_event({"a": "b"}, "/test_event")
  266. self.assertMinionEventReceived({"a": "b"})
  267. Syndic Example via SyndicCase
  268. -----------------------------
  269. Testing Salt's Syndic can be done via the SyndicCase test class:
  270. .. code-block:: python
  271. from tests.support.case import SyndicCase
  272. class TestSyndic(SyndicCase):
  273. """
  274. Validate the syndic interface by testing the test module
  275. """
  276. def test_ping(self):
  277. """
  278. test.ping
  279. """
  280. self.assertTrue(self.run_function("test.ping"))
  281. This example verifies that a ``test.ping`` command is issued from the testing
  282. master, is passed through to the testing syndic, down to the minion, and back
  283. up again by using the ``run_function`` located with in the ``SyndicCase`` test
  284. class.
  285. Integration Test Files
  286. ======================
  287. Since using Salt largely involves configuring states, editing files, and changing
  288. system data, the integration test suite contains a directory named ``files`` to
  289. aid in testing functions that require files. Various Salt integration tests use
  290. these example files to test against instead of altering system files and data.
  291. Each directory within ``tests/integration/files`` contain files that accomplish
  292. different tasks, based on the needs of the integration tests using those files.
  293. For example, ``tests/integration/files/ssh`` is used to bootstrap the test runner
  294. for salt-ssh testing, while ``tests/integration/files/pillar`` contains files
  295. storing data needed to test various pillar functions.
  296. The ``tests/integration/files`` directory also includes an integration state tree.
  297. The integration state tree can be found at ``tests/integration/files/file/base``.
  298. The following example demonstrates how integration files can be used with ModuleCase
  299. to test states:
  300. .. code-block:: python
  301. # Import python libs
  302. from __future__ import absolute_import
  303. import os
  304. import shutil
  305. # Import Salt Testing libs
  306. from tests.support.case import ModuleCase
  307. from tests.support.paths import FILES, TMP
  308. from tests.support.mixins import SaltReturnAssertsMixin
  309. # Import salt libs
  310. import salt.utils.files
  311. HFILE = os.path.join(TMP, "hosts")
  312. class HostTest(ModuleCase, SaltReturnAssertsMixin):
  313. """
  314. Validate the host state
  315. """
  316. def setUp(self):
  317. shutil.copyfile(os.path.join(FILES, "hosts"), HFILE)
  318. super(HostTest, self).setUp()
  319. def tearDown(self):
  320. if os.path.exists(HFILE):
  321. os.remove(HFILE)
  322. super(HostTest, self).tearDown()
  323. def test_present(self):
  324. """
  325. host.present
  326. """
  327. name = "spam.bacon"
  328. ip = "10.10.10.10"
  329. ret = self.run_state("host.present", name=name, ip=ip)
  330. self.assertSaltTrueReturn(ret)
  331. with salt.utils.files.fopen(HFILE) as fp_:
  332. output = fp_.read()
  333. self.assertIn("{0}\t\t{1}".format(ip, name), output)
  334. To access the integration files, a variable named ``FILES`` points to the
  335. ``tests/integration/files`` directory. This is where the referenced
  336. ``host.present`` sls file resides.
  337. In addition to the static files in the integration state tree, the location
  338. ``TMP`` can also be used to store temporary files that the test system
  339. will clean up when the execution finishes.
  340. Destructive vs Non-Destructive Tests
  341. ====================================
  342. Since Salt is used to change the settings and behavior of systems, one testing
  343. approach is to run tests that make actual changes to the underlying system. This
  344. is where the concept of destructive integration tests comes into play. Tests can
  345. be written to alter the system they are running on. This capability is what fills
  346. in the gap needed to properly test aspects of system management like package
  347. installation.
  348. Any test that changes the underlying system in any way, such as creating or
  349. deleting users, installing packages, or changing permissions should include the
  350. ``@destructive`` decorator to signal system changes and should be written with
  351. care. System changes executed within a destructive test should also be restored
  352. once the related tests have completed. For example, if a new user is created to
  353. test a module, the same user should be removed after the test is completed to
  354. maintain system integrity.
  355. To write a destructive test, import, and use the destructiveTest decorator for
  356. the test method:
  357. .. code-block:: python
  358. from tests.support.case import ModuleCase
  359. from tests.support.helpers import destructiveTest, skip_if_not_root
  360. class DestructiveExampleModuleTest(ModuleCase):
  361. """
  362. Demonstrate a destructive test
  363. """
  364. @destructiveTest
  365. @skip_if_not_root
  366. def test_user_not_present(self):
  367. """
  368. This is a DESTRUCTIVE TEST it creates a new user on the minion.
  369. And then destroys that user.
  370. """
  371. ret = self.run_state("user.present", name="salt_test")
  372. self.assertSaltTrueReturn(ret)
  373. ret = self.run_state("user.absent", name="salt_test")
  374. self.assertSaltTrueReturn(ret)
  375. Cloud Provider Tests
  376. ====================
  377. Cloud provider integration tests are used to assess :ref:`Salt-Cloud<salt-cloud>`'s
  378. ability to create and destroy cloud instances for various supported cloud providers.
  379. Cloud provider tests inherit from the ShellCase Integration Class.
  380. Any new cloud provider test files should be added to the ``tests/integration/cloud/providers/``
  381. directory. Each cloud provider test file also requires a sample cloud profile and cloud
  382. provider configuration file in the integration test file directory located at
  383. ``tests/integration/files/conf/cloud.*.d/``.
  384. The following is an example of the default profile configuration file for Digital
  385. Ocean, located at: ``tests/integration/files/conf/cloud.profiles.d/digitalocean.conf``:
  386. .. code-block:: yaml
  387. digitalocean-test:
  388. provider: digitalocean-config
  389. image: Ubuntu 14.04 x64
  390. size: 512MB
  391. Each cloud provider requires different configuration credentials. Therefore, sensitive
  392. information such as API keys or passwords should be omitted from the cloud provider
  393. configuration file and replaced with an empty string. The necessary credentials can
  394. be provided by the user by editing the provider configuration file before running the
  395. tests.
  396. The following is an example of the default provider configuration file for Digital
  397. Ocean, located at: ``tests/integration/files/conf/cloud.providers.d/digitalocean.conf``:
  398. .. code-block:: yaml
  399. digitalocean-config:
  400. driver: digitalocean
  401. client_key: ''
  402. api_key: ''
  403. location: New York 1
  404. In addition to providing the necessary cloud profile and provider files in the integration
  405. test suite file structure, appropriate checks for if the configuration files exist and
  406. contain valid information are also required in the test class's ``setUp`` function:
  407. .. code-block:: python
  408. from tests.support.case import ShellCase
  409. from tests.support.paths import FILES
  410. class LinodeTest(ShellCase):
  411. """
  412. Integration tests for the Linode cloud provider in Salt-Cloud
  413. """
  414. def setUp(self):
  415. """
  416. Sets up the test requirements
  417. """
  418. super(LinodeTest, self).setUp()
  419. # check if appropriate cloud provider and profile files are present
  420. profile_str = "linode-config:"
  421. provider = "linode"
  422. providers = self.run_cloud("--list-providers")
  423. if profile_str not in providers:
  424. self.skipTest(
  425. "Configuration file for {0} was not found. Check {0}.conf files "
  426. "in tests/integration/files/conf/cloud.*.d/ to run these tests.".format(
  427. provider
  428. )
  429. )
  430. # check if apikey and password are present
  431. path = os.path.join(FILES, "conf", "cloud.providers.d", provider + ".conf")
  432. config = cloud_providers_config(path)
  433. api = config["linode-config"]["linode"]["apikey"]
  434. password = config["linode-config"]["linode"]["password"]
  435. if api == "" or password == "":
  436. self.skipTest(
  437. "An api key and password must be provided to run these tests. Check "
  438. "tests/integration/files/conf/cloud.providers.d/{0}.conf".format(
  439. provider
  440. )
  441. )
  442. Repeatedly creating and destroying instances on cloud providers can be costly.
  443. Therefore, cloud provider tests are off by default and do not run automatically. To
  444. run the cloud provider tests, the ``--cloud-provider-tests`` flag must be provided:
  445. .. code-block:: bash
  446. ./tests/runtests.py --cloud-provider-tests
  447. Since cloud provider tests do not run automatically, all provider tests must be
  448. preceded with the ``@expensiveTest`` decorator. The expensive test decorator is
  449. necessary because it signals to the test suite that the
  450. ``--cloud-provider-tests`` flag is required to run the cloud provider tests.
  451. To write a cloud provider test, import, and use the expensiveTest decorator for
  452. the test function:
  453. .. code-block:: python
  454. from tests.support.helpers import expensiveTest
  455. @expensiveTest
  456. def test_instance(self):
  457. """
  458. Test creating an instance on Linode
  459. """
  460. name = "linode-testing"
  461. # create the instance
  462. instance = self.run_cloud("-p linode-test {0}".format(name))
  463. str = " {0}".format(name)
  464. # check if instance with salt installed returned as expected
  465. try:
  466. self.assertIn(str, instance)
  467. except AssertionError:
  468. self.run_cloud("-d {0} --assume-yes".format(name))
  469. raise
  470. # delete the instance
  471. delete = self.run_cloud("-d {0} --assume-yes".format(name))
  472. str = " True"
  473. try:
  474. self.assertIn(str, delete)
  475. except AssertionError:
  476. raise
  477. Adding New Directories
  478. ======================
  479. If the corresponding Salt directory does not exist within
  480. ``tests/integration``, the new directory must be created along with the
  481. appropriate test file to maintain Salt's testing directory structure.
  482. In order for Salt's test suite to recognize tests within the newly
  483. created directory, options to run the new integration tests must be added to
  484. ``tests/runtests.py``. Examples of the necessary options that must be added
  485. can be found here: :blob:`tests/runtests.py`. The functions that need to be
  486. edited are ``setup_additional_options``, ``validate_options``, and
  487. ``run_integration_tests``.