diff options
Diffstat (limited to 'tests/unittests')
-rw-r--r-- | tests/unittests/juju/__init__.py | 1 | ||||
-rw-r--r-- | tests/unittests/juju/data/juju_output_sos.json | 1 | ||||
-rw-r--r-- | tests/unittests/juju/data/juju_output_sos2.json | 1 | ||||
-rw-r--r-- | tests/unittests/juju/juju_cluster_tests.py | 294 | ||||
-rw-r--r-- | tests/unittests/juju/juju_transports_test.py | 85 |
5 files changed, 382 insertions, 0 deletions
diff --git a/tests/unittests/juju/__init__.py b/tests/unittests/juju/__init__.py new file mode 100644 index 00000000..e1758700 --- /dev/null +++ b/tests/unittests/juju/__init__.py @@ -0,0 +1 @@ +# vim: set et ts=4 sw=4 : diff --git a/tests/unittests/juju/data/juju_output_sos.json b/tests/unittests/juju/data/juju_output_sos.json new file mode 100644 index 00000000..908d049a --- /dev/null +++ b/tests/unittests/juju/data/juju_output_sos.json @@ -0,0 +1 @@ +{"model":{"name":"sos","type":"iaas","controller":"local-lxc","cloud":"localhost","region":"localhost","version":"2.9.42","model-status":{"current":"available","since":"06 Apr 2023 11:31:27+08:00"},"sla":"unsupported"},"machines":{"0":{"juju-status":{"current":"started","since":"06 Apr 2023 11:33:59+08:00","version":"2.9.42"},"hostname":"juju-38ab8b-0","dns-name":"10.224.139.234","ip-addresses":["10.224.139.234"],"instance-id":"juju-38ab8b-0","machine-status":{"current":"running","message":"Running","since":"06 Apr 2023 11:31:58+08:00"},"modification-status":{"current":"applied","since":"06 Apr 2023 11:31:56+08:00"},"series":"focal","network-interfaces":{"eth0":{"ip-addresses":["10.224.139.234"],"mac-address":"00:16:3e:52:05:7f","gateway":"10.224.139.1","space":"alpha","is-up":true}},"constraints":"arch=amd64","hardware":"arch=amd64 cores=0 mem=0M"},"2":{"juju-status":{"current":"started","since":"07 Apr 2023 14:41:09+08:00","version":"2.9.42"},"hostname":"juju-38ab8b-2","dns-name":"10.224.139.188","ip-addresses":["10.224.139.188"],"instance-id":"juju-38ab8b-2","machine-status":{"current":"running","message":"Running","since":"07 Apr 2023 14:39:09+08:00"},"modification-status":{"current":"applied","since":"07 Apr 2023 14:39:06+08:00"},"series":"focal","network-interfaces":{"eth0":{"ip-addresses":["10.224.139.188"],"mac-address":"00:16:3e:44:31:2d","gateway":"10.224.139.1","space":"alpha","is-up":true}},"constraints":"arch=amd64","hardware":"arch=amd64 cores=0 mem=0M"},"3":{"juju-status":{"current":"started","since":"07 Apr 2023 14:41:08+08:00","version":"2.9.42"},"hostname":"juju-38ab8b-3","dns-name":"10.224.139.181","ip-addresses":["10.224.139.181"],"instance-id":"juju-38ab8b-3","machine-status":{"current":"running","message":"Running","since":"07 Apr 2023 14:39:09+08:00"},"modification-status":{"current":"applied","since":"07 Apr 2023 14:39:06+08:00"},"series":"focal","network-interfaces":{"eth0":{"ip-addresses":["10.224.139.181"],"mac-address":"00:16:3e:44:80:a9","gateway":"10.224.139.1","space":"alpha","is-up":true}},"constraints":"arch=amd64","hardware":"arch=amd64 cores=0 mem=0M"},"4":{"juju-status":{"current":"started","since":"07 Apr 2023 17:01:12+08:00","version":"2.9.42"},"hostname":"juju-38ab8b-4","dns-name":"10.224.139.114","ip-addresses":["10.224.139.114"],"instance-id":"juju-38ab8b-4","machine-status":{"current":"running","message":"Running","since":"07 Apr 2023 16:59:27+08:00"},"modification-status":{"current":"applied","since":"07 Apr 2023 16:59:26+08:00"},"series":"jammy","network-interfaces":{"eth0":{"ip-addresses":["10.224.139.114"],"mac-address":"00:16:3e:0f:84:45","gateway":"10.224.139.1","space":"alpha","is-up":true}},"constraints":"arch=amd64","hardware":"arch=amd64 cores=0 mem=0M"}},"applications":{"nginx":{"charm":"nginx","series":"jammy","os":"ubuntu","charm-origin":"charmhub","charm-name":"nginx","charm-rev":6,"charm-channel":"stable","exposed":false,"application-status":{"current":"unknown","since":"07 Apr 2023 16:59:17+08:00"},"units":{"nginx/1":{"workload-status":{"current":"unknown","since":"07 Apr 2023 17:01:28+08:00"},"juju-status":{"current":"idle","since":"07 Apr 2023 17:01:28+08:00","version":"2.9.42"},"leader":true,"machine":"4","public-address":"10.224.139.114"}},"endpoint-bindings":{"":"alpha","publish":"alpha"}},"nrpe":{"charm":"nrpe","series":"jammy","os":"ubuntu","charm-origin":"charmhub","charm-name":"nrpe","charm-rev":97,"charm-channel":"stable","exposed":false,"application-status":{"current":"active","message":"Ready","since":"06 Apr 2023 11:38:44+08:00"},"relations":{"general-info":["ubuntu"]},"subordinate-to":["ubuntu"],"endpoint-bindings":{"":"alpha","general-info":"alpha","local-monitors":"alpha","monitors":"alpha","nrpe":"alpha","nrpe-external-master":"alpha"}},"ubuntu":{"charm":"ubuntu","series":"focal","os":"ubuntu","charm-origin":"charmhub","charm-name":"ubuntu","charm-rev":22,"charm-channel":"stable","exposed":false,"application-status":{"current":"active","since":"06 Apr 2023 11:34:02+08:00"},"relations":{"juju-info":["nrpe"]},"units":{"ubuntu/0":{"workload-status":{"current":"active","since":"06 Apr 2023 11:34:02+08:00"},"juju-status":{"current":"idle","since":"06 Apr 2023 11:34:04+08:00","version":"2.9.42"},"leader":true,"machine":"0","public-address":"10.224.139.234","subordinates":{"nrpe/0":{"workload-status":{"current":"active","message":"Ready","since":"06 Apr 2023 11:38:44+08:00"},"juju-status":{"current":"idle","since":"06 Apr 2023 11:34:30+08:00","version":"2.9.42"},"leader":true,"open-ports":["icmp","5666/tcp"],"public-address":"10.224.139.234"}}},"ubuntu/1":{"workload-status":{"current":"active","since":"07 Apr 2023 14:41:13+08:00"},"juju-status":{"current":"idle","since":"07 Apr 2023 14:41:15+08:00","version":"2.9.42"},"machine":"2","public-address":"10.224.139.188","subordinates":{"nrpe/2":{"workload-status":{"current":"active","message":"Ready","since":"07 Apr 2023 14:45:38+08:00"},"juju-status":{"current":"idle","since":"07 Apr 2023 14:41:41+08:00","version":"2.9.42"},"open-ports":["icmp","5666/tcp"],"public-address":"10.224.139.188"}}},"ubuntu/2":{"workload-status":{"current":"active","since":"07 Apr 2023 14:41:11+08:00"},"juju-status":{"current":"idle","since":"07 Apr 2023 14:41:13+08:00","version":"2.9.42"},"machine":"3","public-address":"10.224.139.181","subordinates":{"nrpe/1":{"workload-status":{"current":"active","message":"Ready","since":"07 Apr 2023 14:46:56+08:00"},"juju-status":{"current":"idle","since":"07 Apr 2023 14:41:41+08:00","version":"2.9.42"},"open-ports":["icmp","5666/tcp"],"public-address":"10.224.139.181"}}}}}},"storage":{"storage":{"files/0":{"kind":"filesystem","life":"alive","status":{"current":"attached","since":"06 Apr 2023 11:34:01+08:00"},"persistent":false,"attachments":{"units":{"ubuntu/0":{"machine":"0","location":"/srv/data","life":"alive"}}}},"files/1":{"kind":"filesystem","life":"alive","status":{"current":"attached","since":"07 Apr 2023 14:41:11+08:00"},"persistent":false,"attachments":{"units":{"ubuntu/1":{"machine":"2","location":"/srv/data","life":"alive"}}}},"files/2":{"kind":"filesystem","life":"alive","status":{"current":"attached","since":"07 Apr 2023 14:41:10+08:00"},"persistent":false,"attachments":{"units":{"ubuntu/2":{"machine":"3","location":"/srv/data","life":"alive"}}}}},"filesystems":{"0/0":{"provider-id":"0/0","storage":"files/0","Attachments":{"machines":{"0":{"mount-point":"/srv/data","read-only":false,"life":"alive"}},"units":{"ubuntu/0":{"machine":"0","location":"/srv/data","life":"alive"}}},"pool":"rootfs","size":12464,"life":"alive","status":{"current":"attached","since":"06 Apr 2023 11:34:01+08:00"}},"2/1":{"provider-id":"2/1","storage":"files/1","Attachments":{"machines":{"2":{"mount-point":"/srv/data","read-only":false,"life":"alive"}},"units":{"ubuntu/1":{"machine":"2","location":"/srv/data","life":"alive"}}},"pool":"rootfs","size":10062,"life":"alive","status":{"current":"attached","since":"07 Apr 2023 14:41:11+08:00"}},"3/2":{"provider-id":"3/2","storage":"files/2","Attachments":{"machines":{"3":{"mount-point":"/srv/data","read-only":false,"life":"alive"}},"units":{"ubuntu/2":{"machine":"3","location":"/srv/data","life":"alive"}}},"pool":"rootfs","size":10113,"life":"alive","status":{"current":"attached","since":"07 Apr 2023 14:41:10+08:00"}}}},"controller":{"timestamp":"17:04:14+08:00"}} diff --git a/tests/unittests/juju/data/juju_output_sos2.json b/tests/unittests/juju/data/juju_output_sos2.json new file mode 100644 index 00000000..4a41bba1 --- /dev/null +++ b/tests/unittests/juju/data/juju_output_sos2.json @@ -0,0 +1 @@ +{"model":{"name":"sos2","type":"iaas","controller":"local-lxc","cloud":"localhost","region":"localhost","version":"2.9.42","model-status":{"current":"available","since":"07 Apr 2023 12:54:21+08:00"},"sla":"unsupported"},"machines":{"0":{"juju-status":{"current":"started","since":"07 Apr 2023 12:57:16+08:00","version":"2.9.42"},"hostname":"juju-1cba19-0","dns-name":"10.224.139.132","ip-addresses":["10.224.139.132"],"instance-id":"juju-1cba19-0","machine-status":{"current":"running","message":"Running","since":"07 Apr 2023 12:55:16+08:00"},"modification-status":{"current":"applied","since":"07 Apr 2023 12:55:13+08:00"},"series":"focal","network-interfaces":{"eth0":{"ip-addresses":["10.224.139.132"],"mac-address":"00:16:3e:55:4b:a9","gateway":"10.224.139.1","space":"alpha","is-up":true}},"constraints":"arch=amd64","hardware":"arch=amd64 cores=0 mem=0M"},"1":{"juju-status":{"current":"started","since":"07 Apr 2023 12:57:17+08:00","version":"2.9.42"},"hostname":"juju-1cba19-1","dns-name":"10.224.139.94","ip-addresses":["10.224.139.94"],"instance-id":"juju-1cba19-1","machine-status":{"current":"running","message":"Running","since":"07 Apr 2023 12:55:16+08:00"},"modification-status":{"current":"applied","since":"07 Apr 2023 12:55:15+08:00"},"series":"focal","network-interfaces":{"eth0":{"ip-addresses":["10.224.139.94"],"mac-address":"00:16:3e:5d:d8:08","gateway":"10.224.139.1","space":"alpha","is-up":true}},"constraints":"arch=amd64","hardware":"arch=amd64 cores=0 mem=0M"}},"applications":{"ubuntu":{"charm":"ubuntu","series":"focal","os":"ubuntu","charm-origin":"charmhub","charm-name":"ubuntu","charm-rev":22,"charm-channel":"stable","exposed":false,"application-status":{"current":"active","since":"07 Apr 2023 12:57:19+08:00"},"units":{"ubuntu/0":{"workload-status":{"current":"active","since":"07 Apr 2023 12:57:19+08:00"},"juju-status":{"current":"idle","since":"07 Apr 2023 12:57:21+08:00","version":"2.9.42"},"leader":true,"machine":"0","public-address":"10.224.139.132"},"ubuntu/1":{"workload-status":{"current":"active","since":"07 Apr 2023 12:57:20+08:00"},"juju-status":{"current":"idle","since":"07 Apr 2023 12:57:22+08:00","version":"2.9.42"},"machine":"1","public-address":"10.224.139.94"}},"version":"20.04"}},"storage":{"storage":{"files/0":{"kind":"filesystem","life":"alive","status":{"current":"attached","since":"07 Apr 2023 12:57:18+08:00"},"persistent":false,"attachments":{"units":{"ubuntu/0":{"machine":"0","location":"/srv/data","life":"alive"}}}},"files/1":{"kind":"filesystem","life":"alive","status":{"current":"attached","since":"07 Apr 2023 12:57:19+08:00"},"persistent":false,"attachments":{"units":{"ubuntu/1":{"machine":"1","location":"/srv/data","life":"alive"}}}}},"filesystems":{"0/0":{"provider-id":"0/0","storage":"files/0","Attachments":{"machines":{"0":{"mount-point":"/srv/data","read-only":false,"life":"alive"}},"units":{"ubuntu/0":{"machine":"0","location":"/srv/data","life":"alive"}}},"pool":"rootfs","size":11131,"life":"alive","status":{"current":"attached","since":"07 Apr 2023 12:57:18+08:00"}},"1/1":{"provider-id":"1/1","storage":"files/1","Attachments":{"machines":{"1":{"mount-point":"/srv/data","read-only":false,"life":"alive"}},"units":{"ubuntu/1":{"machine":"1","location":"/srv/data","life":"alive"}}},"pool":"rootfs","size":11094,"life":"alive","status":{"current":"attached","since":"07 Apr 2023 12:57:19+08:00"}}}},"controller":{"timestamp":"14:16:44+08:00"}} diff --git a/tests/unittests/juju/juju_cluster_tests.py b/tests/unittests/juju/juju_cluster_tests.py new file mode 100644 index 00000000..136dc49f --- /dev/null +++ b/tests/unittests/juju/juju_cluster_tests.py @@ -0,0 +1,294 @@ +# Copyright (c) 2023 Canonical Ltd., Chi Wai Chan <chiwai.chan@canonical.com> + +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. +import pathlib +import unittest +from unittest.mock import call, patch + +from sos.collector.clusters.juju import _parse_option_string, juju +from sos.options import ClusterOption + + +class MockOptions: + + def __init__(self): + self.cluster_options = [] + + +def get_juju_output(model): + dir = pathlib.Path(__file__).parent.resolve() + with open(dir / "data" / f"juju_output_{model}.json") as f: + return f.read() + + +def get_juju_status(cmd): + if "-m" in cmd: + model = cmd.split()[3] + else: + model = "sos" + + return { + "status": 0, + "output": get_juju_output(model), + } + + +def test_parse_option_string(): + result = _parse_option_string(" a,b,c") + assert result == ["a", "b", "c"] + + result = _parse_option_string() + assert result == [] + + +class JujuTest(unittest.TestCase): + """Test for juju cluster.""" + + @patch( + "sos.collector.clusters.juju.juju.exec_primary_cmd", + side_effect=get_juju_status, + ) + def test_get_nodes_no_filter(self, mock_exec_primary_cmd): + """No filter.""" + mock_opts = MockOptions() + cluster = juju( + commons={ + "tmpdir": "/tmp", + "cmdlineopts": mock_opts, + } + ) + nodes = cluster.get_nodes() + assert nodes == [] + + @patch( + "sos.collector.clusters.juju.juju.exec_primary_cmd", + side_effect=get_juju_status, + ) + def test_get_nodes_app_filter(self, mock_exec_primary_cmd): + """Application filter.""" + mock_opts = MockOptions() + mock_opts.cluster_options.append( + ClusterOption( + name="apps", + opt_type=str, + value="ubuntu", + cluster=juju.__name__, + ) + ) + cluster = juju( + commons={ + "tmpdir": "/tmp", + "cmdlineopts": mock_opts, + } + ) + nodes = cluster.get_nodes() + nodes.sort() + assert nodes == [":0", ":2", ":3"] + mock_exec_primary_cmd.assert_called_once_with( + "juju status --format json" + ) + + @patch( + "sos.collector.clusters.juju.juju.exec_primary_cmd", + side_effect=get_juju_status, + ) + def test_get_nodes_app_regex_filter(self, mock_exec_primary_cmd): + """Application filter.""" + mock_opts = MockOptions() + mock_opts.cluster_options.append( + ClusterOption( + name="apps", + opt_type=str, + value="ubuntu|nginx", + cluster=juju.__name__, + ) + ) + cluster = juju( + commons={ + "tmpdir": "/tmp", + "cmdlineopts": mock_opts, + } + ) + nodes = cluster.get_nodes() + nodes.sort() + assert nodes == [":0", ":2", ":3", ":4"] + mock_exec_primary_cmd.assert_called_once_with( + "juju status --format json" + ) + + @patch( + "sos.collector.clusters.juju.juju.exec_primary_cmd", + side_effect=get_juju_status, + ) + def test_get_nodes_model_filter_multiple_models( + self, mock_exec_primary_cmd + ): + """Multiple model filter.""" + mock_opts = MockOptions() + mock_opts.cluster_options.append( + ClusterOption( + name="models", + opt_type=str, + value="sos,sos2", + cluster=juju.__name__, + ), + ) + mock_opts.cluster_options.append( + ClusterOption( + name="apps", + opt_type=str, + value="ubuntu", + cluster=juju.__name__, + ), + ) + cluster = juju( + commons={ + "tmpdir": "/tmp", + "cmdlineopts": mock_opts, + } + ) + nodes = cluster.get_nodes() + nodes.sort() + assert nodes == [ + "sos2:0", + "sos2:1", + "sos:0", + "sos:2", + "sos:3", + ] + mock_exec_primary_cmd.assert_has_calls( + [ + call("juju status -m sos --format json"), + call("juju status -m sos2 --format json"), + ] + ) + + @patch( + "sos.collector.clusters.juju.juju.exec_primary_cmd", + side_effect=get_juju_status, + ) + def test_get_nodes_model_filter(self, mock_exec_primary_cmd): + """Model filter.""" + mock_opts = MockOptions() + mock_opts.cluster_options.append( + ClusterOption( + name="models", + opt_type=str, + value="sos", + cluster=juju.__name__, + ) + ) + mock_opts.cluster_options.append( + ClusterOption( + name="apps", + opt_type=str, + value="ubuntu", + cluster=juju.__name__, + ), + ) + cluster = juju( + commons={ + "tmpdir": "/tmp", + "cmdlineopts": mock_opts, + } + ) + nodes = cluster.get_nodes() + nodes.sort() + assert nodes == [ + "sos:0", + "sos:2", + "sos:3", + ] + mock_exec_primary_cmd.assert_has_calls( + [ + call("juju status -m sos --format json"), + ] + ) + + @patch( + "sos.collector.clusters.juju.juju.exec_primary_cmd", + side_effect=get_juju_status, + ) + def test_get_nodes_unit_filter(self, mock_exec_primary_cmd): + """Node filter.""" + mock_opts = MockOptions() + mock_opts.cluster_options.append( + ClusterOption( + name="units", + opt_type=str, + value="ubuntu/0,ubuntu/1", + cluster=juju.__name__, + ) + ) + cluster = juju( + commons={ + "tmpdir": "/tmp", + "cmdlineopts": mock_opts, + } + ) + nodes = cluster.get_nodes() + nodes.sort() + assert nodes == [":0", ":2"] + + @patch( + "sos.collector.clusters.juju.juju.exec_primary_cmd", + side_effect=get_juju_status, + ) + def test_get_nodes_machine_filter(self, mock_exec_primary_cmd): + """Machine filter.""" + mock_opts = MockOptions() + mock_opts.cluster_options.append( + ClusterOption( + name="machines", + opt_type=str, + value="0,2", + cluster=juju.__name__, + ) + ) + cluster = juju( + commons={ + "tmpdir": "/tmp", + "cmdlineopts": mock_opts, + } + ) + nodes = cluster.get_nodes() + nodes.sort() + print(nodes) + assert nodes == [":0", ":2"] + + @patch( + "sos.collector.clusters.juju.juju.exec_primary_cmd", + side_effect=get_juju_status, + ) + def test_subordinates(self, mock_exec_primary_cmd): + """Subordinate filter.""" + mock_opts = MockOptions() + mock_opts.cluster_options.append( + ClusterOption( + name="apps", + opt_type=str, + value="nrpe", + cluster=juju.__name__, + ) + ) + cluster = juju( + commons={ + "tmpdir": "/tmp", + "cmdlineopts": mock_opts, + } + ) + nodes = cluster.get_nodes() + nodes.sort() + assert nodes == [":0", ":2", ":3"] + mock_exec_primary_cmd.assert_called_once_with( + "juju status --format json" + ) + + +# vim: set et ts=4 sw=4 : diff --git a/tests/unittests/juju/juju_transports_test.py b/tests/unittests/juju/juju_transports_test.py new file mode 100644 index 00000000..911a957e --- /dev/null +++ b/tests/unittests/juju/juju_transports_test.py @@ -0,0 +1,85 @@ +# Copyright (c) 2023 Canonical Ltd., Chi Wai Chan <chiwai.chan@canonical.com> + +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + +import subprocess +import unittest +from unittest.mock import patch + +from sos.collector.exceptions import JujuNotInstalledException +from sos.collector.transports.juju import JujuSSH + + +class MockCmdLineOpts(object): + ssh_user = "user_abc" + sudo_pw = "pw_abc" + root_password = "root_pw_abc" + + +class JujuSSHTest(unittest.TestCase): + def setUp(self): + self.juju_ssh = JujuSSH( + commons={ + "cmdlineopts": MockCmdLineOpts, + "tmpdir": "/tmp/sos-juju/", + "need_sudo": False, + }, + address="model_abc:unit_abc", + ) + + @patch("sos.collector.transports.juju.subprocess.check_output") + def test_check_juju_installed_err(self, mock_subprocess_check_output): + """Raise error if juju is not installed.""" + mock_subprocess_check_output.side_effect = ( + subprocess.CalledProcessError(returncode="127", cmd="cmd_abc") + ) + with self.assertRaises(JujuNotInstalledException): + self.juju_ssh._check_juju_installed() + + @patch("sos.collector.transports.juju.subprocess.check_output") + def test_check_juju_installed_true(self, mock_subprocess_check_output): + """Return True if juju is installed.""" + result = self.juju_ssh._check_juju_installed() + assert result + + @patch("sos.collector.transports.juju.subprocess.check_output") + def test_chmod(self, mock_subprocess_check_output): + self.juju_ssh._chmod(fname="file_abc") + mock_subprocess_check_output.assert_called_with( + f"{self.juju_ssh.remote_exec} sudo chmod o+r file_abc", + stderr=subprocess.STDOUT, + shell=True, + ) + + @patch( + "sos.collector.transports.juju.JujuSSH._check_juju_installed", + return_value=True, + ) + def test_connect(self, mock_result): + self.juju_ssh.connect(password=None) + assert self.juju_ssh.connected + + def test_remote_exec(self): + assert ( + self.juju_ssh.remote_exec == "juju ssh -m model_abc unit_abc" + ) + + @patch( + "sos.collector.transports.juju.sos_get_command_output", + return_value={"status": 0}, + ) + @patch("sos.collector.transports.juju.JujuSSH._chmod", return_value=True) + def test_retrieve_file(self, mock_chmod, mock_sos_get_cmd_output): + self.juju_ssh._retrieve_file(fname="file_abc", dest="/tmp/sos-juju/") + mock_sos_get_cmd_output.assert_called_with( + "juju scp -m model_abc -- -r unit_abc:file_abc /tmp/sos-juju/" + ) + + +# vim: set et ts=4 sw=4 : |