1"""
2    :codeauthor: Nicole Thomas <nicole@saltstack.com>
3"""
4
5import os
6import traceback
7
8import pytest
9import salt.config
10import salt.utils.yaml
11from salt.output import display_output
12from saltfactories.utils.tempfiles import temp_file
13from tests.support.case import ShellCase
14from tests.support.mixins import RUNTIME_VARS
15
16
17class OutputReturnTest(ShellCase):
18    """
19    Integration tests to ensure outputters return their expected format.
20    Tests against situations where the loader might not be returning the
21    right outputter even though it was explicitly requested.
22    """
23
24    @pytest.mark.slow_test
25    def test_output_json(self):
26        """
27        Tests the return of json-formatted data
28        """
29        ret = self.run_call("test.ping --out=json")
30        self.assertIn("{", ret)
31        self.assertIn('"local": true', "".join(ret))
32        self.assertIn("}", "".join(ret))
33
34    @pytest.mark.slow_test
35    def test_output_nested(self):
36        """
37        Tests the return of nested-formatted data
38        """
39        expected = ["local:", "    True"]
40        ret = self.run_call("test.ping --out=nested")
41        self.assertEqual(ret, expected)
42
43    @pytest.mark.slow_test
44    def test_output_quiet(self):
45        """
46        Tests the return of an out=quiet query
47        """
48        expected = []
49        ret = self.run_call("test.ping --out=quiet")
50        self.assertEqual(ret, expected)
51
52    @pytest.mark.slow_test
53    def test_output_pprint(self):
54        """
55        Tests the return of pprint-formatted data
56        """
57        expected = ["{'local': True}"]
58        ret = self.run_call("test.ping --out=pprint")
59        self.assertEqual(ret, expected)
60
61    @pytest.mark.slow_test
62    def test_output_raw(self):
63        """
64        Tests the return of raw-formatted data
65        """
66        expected = ["{'local': True}"]
67        ret = self.run_call("test.ping --out=raw")
68        self.assertEqual(ret, expected)
69
70    @pytest.mark.slow_test
71    def test_output_txt(self):
72        """
73        Tests the return of txt-formatted data
74        """
75        expected = ["local: True"]
76        ret = self.run_call("test.ping --out=txt")
77        self.assertEqual(ret, expected)
78
79    @pytest.mark.slow_test
80    def test_output_yaml(self):
81        """
82        Tests the return of yaml-formatted data
83        """
84        expected = ["local: true"]
85        ret = self.run_call("test.ping --out=yaml")
86        self.assertEqual(ret, expected)
87
88    @pytest.mark.slow_test
89    def test_output_yaml_namespaced_dict_wrapper(self):
90        """
91        Tests the ability to dump a NamespacedDictWrapper instance, as used in
92        magic dunders like __grains__ and __pillar__
93
94        See https://github.com/saltstack/salt/issues/49269
95        """
96        dumped_yaml = "\n".join(self.run_call("grains.items --out=yaml"))
97        loaded_yaml = salt.utils.yaml.safe_load(dumped_yaml)
98        # We just want to check that the dumped YAML loades as a dict with a
99        # single top-level key, we don't care about the real contents.
100        assert isinstance(loaded_yaml, dict)
101        assert list(loaded_yaml) == ["local"]
102
103    def test_output_unicodebad(self):
104        """
105        Tests outputter reliability with utf8
106        """
107        opts = salt.config.minion_config(
108            os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "minion")
109        )
110        opts["output_file"] = os.path.join(RUNTIME_VARS.TMP, "outputtest")
111        data = {"foo": {"result": False, "aaa": "azerzaeréééé", "comment": "ééééàààà"}}
112        try:
113            # this should not raises UnicodeEncodeError
114            display_output(data, opts=opts)
115        except Exception:  # pylint: disable=broad-except
116            # display trace in error message for debugging on jenkins
117            trace = traceback.format_exc()
118            sentinel = object()
119            old_max_diff = getattr(self, "maxDiff", sentinel)
120            try:
121                self.maxDiff = None
122                self.assertEqual(trace, "")
123            finally:
124                if old_max_diff is sentinel:
125                    delattr(self, "maxDiff")
126                else:
127                    self.maxDiff = old_max_diff
128
129    @pytest.mark.slow_test
130    def test_output_highstate(self):
131        """
132        Regression tests for the highstate outputter. Calls a basic state with various
133        flags. Each comparison should be identical when successful.
134        """
135        simple_ping_sls = """
136        simple-ping:
137          module.run:
138            - name: test.ping
139        """
140        with temp_file(
141            "simple-ping.sls", simple_ping_sls, RUNTIME_VARS.TMP_BASEENV_STATE_TREE
142        ):
143            # Test basic highstate output. No frills.
144            expected = [
145                "minion:",
146                "          ID: simple-ping",
147                "    Function: module.run",
148                "        Name: test.ping",
149                "      Result: True",
150                "     Comment: Module function test.ping executed",
151                "     Changes:   ",
152                "              ret:",
153                "                  True",
154                "Summary for minion",
155                "Succeeded: 1 (changed=1)",
156                "Failed:    0",
157                "Total states run:     1",
158            ]
159            state_run = self.run_salt('"minion" state.sls simple-ping')
160
161            for expected_item in expected:
162                self.assertIn(expected_item, state_run)
163
164            # Test highstate output while also passing --out=highstate.
165            # This is a regression test for Issue #29796
166            state_run = self.run_salt('"minion" state.sls simple-ping --out=highstate')
167
168            for expected_item in expected:
169                self.assertIn(expected_item, state_run)
170
171            # Test highstate output when passing --static and running a state function.
172            # See Issue #44556.
173            state_run = self.run_salt('"minion" state.sls simple-ping --static')
174
175            for expected_item in expected:
176                self.assertIn(expected_item, state_run)
177
178            # Test highstate output when passing --static and --out=highstate.
179            # See Issue #44556.
180            state_run = self.run_salt(
181                '"minion" state.sls simple-ping --static --out=highstate'
182            )
183
184            for expected_item in expected:
185                self.assertIn(expected_item, state_run)
186
187    @pytest.mark.slow_test
188    def test_output_highstate_falls_back_nested(self):
189        """
190        Tests outputter when passing --out=highstate with a non-state call. This should
191        fall back to "nested" output.
192        """
193        expected = ["minion:", "    True"]
194        ret = self.run_salt('"minion" test.ping --out=highstate')
195        self.assertEqual(ret, expected)
196
197    @pytest.mark.slow_test
198    def test_static_simple(self):
199        """
200        Tests passing the --static option with a basic test.ping command. This
201        should be the "nested" output.
202        """
203        expected = ["minion:", "    True"]
204        ret = self.run_salt('"minion" test.ping --static')
205        self.assertEqual(ret, expected)
206