├── .fmf └── version ├── .github ├── release.eloquent.yml └── workflows │ ├── pages.yml │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── COPYING ├── NEWS ├── README.md ├── dbusmock ├── __init__.py ├── __main__.py ├── mockobject.py ├── pytest_fixtures.py ├── templates │ ├── SKELETON │ ├── __init__.py │ ├── bluez5-obex.py │ ├── bluez5.py │ ├── gnome_screensaver.py │ ├── gsd_rfkill.py │ ├── iio-sensors-proxy.py │ ├── logind.py │ ├── low_memory_monitor.py │ ├── modemmanager.py │ ├── networkmanager.py │ ├── notification_daemon.py │ ├── ofono.py │ ├── polkitd.py │ ├── power_profiles_daemon.py │ ├── systemd.py │ ├── timedated.py │ ├── upower.py │ ├── upower_power_profiles_daemon.py │ └── urfkill.py └── testcase.py ├── doc ├── conf.py └── index.rst ├── packaging └── python-dbusmock.spec ├── packit.yaml ├── plans └── all.fmf ├── pyproject.toml ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── conftest.py ├── main.fmf ├── run ├── run-centos ├── run-debian ├── run-fedora ├── run-ubuntu ├── test_api.py ├── test_api_pytest.py ├── test_bluez5.py ├── test_cli.py ├── test_code.py ├── test_gnome_screensaver.py ├── test_gsd_rfkill.py ├── test_iio_sensors_proxy.py ├── test_logind.py ├── test_low_memory_monitor.py ├── test_modemmanager.py ├── test_networkmanager.py ├── test_notification_daemon.py ├── test_ofono.py ├── test_polkitd.py ├── test_power_profiles_daemon.py ├── test_systemd.py ├── test_timedated.py ├── test_upower.py └── test_urfkill.py /.fmf/version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /.github/release.eloquent.yml: -------------------------------------------------------------------------------- 1 | assets: 2 | - path: dist/python_dbusmock*.tar.* 3 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub pages 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | 7 | # Allow running this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | # Allow only one concurrent deployment 11 | concurrency: 12 | group: "pages" 13 | cancel-in-progress: false 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: read 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Set up Python 25 | uses: actions/setup-python@v4 26 | 27 | - name: Install dependencies from pip 28 | run: pip install sphinx sphinx-rtd-theme sphinx-autoapi myst-parser 29 | 30 | - name: Build with sphinx 31 | run: sphinx-build -M html doc/ doc/ 32 | 33 | - name: Move output to target directory 34 | run: mv doc/html public_html/ 35 | 36 | - name: Upload pages artifact 37 | id: deployment 38 | uses: actions/upload-pages-artifact@v3 39 | with: 40 | path: public_html/ 41 | 42 | deploy: 43 | needs: build 44 | runs-on: ubuntu-latest 45 | permissions: 46 | contents: read 47 | pages: write 48 | id-token: write 49 | environment: 50 | name: github-pages 51 | url: ${{ steps.deployment.outputs.page_url }} 52 | 53 | steps: 54 | - name: Deploy to GitHub Pages 55 | id: deployment 56 | uses: actions/deploy-pages@v4 57 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: tag 2 | on: 3 | push: 4 | tags: 5 | # this is a glob, not a regexp 6 | - '[0-9]*' 7 | jobs: 8 | release: 9 | runs-on: ubuntu-24.04 10 | environment: release 11 | permissions: 12 | # PyPI trusted publisher 13 | id-token: write 14 | # create release 15 | contents: write 16 | steps: 17 | - name: Clone repository 18 | uses: actions/checkout@v4 19 | with: 20 | # need this to also fetch tags 21 | fetch-depth: 0 22 | 23 | - name: Workaround for https://github.com/actions/checkout/pull/697 24 | run: git fetch --force origin $(git describe --tags):refs/tags/$(git describe --tags) 25 | 26 | - name: Install dependencies 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install -y python3-build python3-setuptools python3-setuptools-scm python3-venv 30 | 31 | - name: Build release tarball 32 | run: python3 -m build --sdist 33 | 34 | - name: Sanity check 35 | run: | 36 | set -eux 37 | mkdir tmp 38 | cd tmp 39 | tar xf ../dist/python_dbusmock-${{ github.ref_name }}.tar.gz 40 | cd python_dbusmock-* 41 | test "$(PYTHONPATH=. python3 -c 'import dbusmock; print(dbusmock.__version__)')" = "${{ github.ref_name }}" 42 | PYTHONPATH=. python3 tests/test_api.py 43 | cd ../.. 44 | rm -rf tmp 45 | 46 | - name: Create GitHub release 47 | uses: cockpit-project/action-release@7d2e2657382e8d34f88a24b5987f2b81ea165785 48 | with: 49 | filename: "dist/python_dbusmock-${{ github.ref_name }}.tar.gz" 50 | 51 | - name: Create PyPy release 52 | uses: pypa/gh-action-pypi-publish@release/v1 53 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: unit-tests 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: 0 4 * * MON,FRI 7 | jobs: 8 | OS: 9 | runs-on: ubuntu-24.04 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | scenario: 14 | - docker.io/debian:unstable 15 | - docker.io/ubuntu:devel 16 | - docker.io/ubuntu:rolling 17 | - docker.io/ubuntu:latest 18 | - registry.fedoraproject.org/fedora:latest 19 | - registry.fedoraproject.org/fedora:rawhide 20 | - quay.io/centos/centos:stream9 21 | - quay.io/centos/centos:stream10-development 22 | 23 | timeout-minutes: 30 24 | steps: 25 | - name: Clone repository 26 | uses: actions/checkout@v4 27 | with: 28 | # need this to also fetch tags 29 | fetch-depth: 0 30 | 31 | - name: Workaround for https://github.com/actions/checkout/pull/697 32 | run: | 33 | set -ex 34 | TAG=$(git describe --tags) 35 | if echo "$TAG" | grep -q '^[0-9.]\+$'; then 36 | git fetch --force origin $TAG:refs/tags/$TAG 37 | fi 38 | 39 | - name: Run unit tests 40 | run: | 41 | dpkg -s podman docker || true 42 | cat /etc/apt/sources.list 43 | cat /etc/apt/sources.list.d/* 44 | ${{ matrix.env }} tests/run ${{ matrix.scenario }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | /build 3 | /dbusmock/_version.py 4 | /dist 5 | /doc/doctrees 6 | /doc/html 7 | MANIFEST 8 | __pycache__ 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: end-of-file-fixer 6 | - id: trailing-whitespace 7 | - id: check-merge-conflict 8 | - id: check-toml 9 | - id: check-symlinks 10 | - id: no-commit-to-branch 11 | args: ['--branch', 'main'] 12 | - repo: https://github.com/psf/black 13 | rev: 23.11.0 14 | hooks: 15 | - id: black 16 | args: ['--check', '--diff', '.'] 17 | - repo: https://github.com/charliermarsh/ruff-pre-commit 18 | rev: v0.1.6 19 | hooks: 20 | - id: ruff 21 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![CI status](https://github.com/martinpitt/python-dbusmock/actions/workflows/tests.yml/badge.svg) 2 | 3 | python-dbusmock 4 | =============== 5 | 6 | ## Purpose 7 | 8 | With this program/Python library you can easily create mock objects on 9 | D-Bus. This is useful for writing tests for software which talks to 10 | D-Bus services such as upower, systemd, logind, gnome-session or others, 11 | and it is hard (or impossible without root privileges) to set the state 12 | of the real services to what you expect in your tests. 13 | 14 | Suppose you want to write tests for gnome-settings-daemon's power 15 | plugin, or another program that talks to upower. You want to verify that 16 | after the configured idle time the program suspends the machine. So your 17 | program calls `org.freedesktop.UPower.Suspend()` on the system D-Bus. 18 | 19 | Now, your test suite should not really talk to the actual system D-Bus 20 | and the real upower; a `make check` that suspends your machine will not 21 | be considered very friendly by most people, and if you want to run this 22 | in continuous integration test servers or package build environments, 23 | chances are that your process does not have the privilege to suspend, or 24 | there is no system bus or upower to begin with. Likewise, there is no 25 | way for an user process to forcefully set the system/seat idle flag in 26 | logind, so your tests cannot set up the expected test environment on the 27 | real daemon. 28 | 29 | That's where mock objects come into play: They look like the real API 30 | (or at least the parts that you actually need), but they do not actually 31 | do anything (or only some action that you specify yourself). You can 32 | configure their state, behaviour and responses as you like in your test, 33 | without making any assumptions about the real system status. 34 | 35 | When using a local system/session bus, you can do unit or integration 36 | testing without needing root privileges or disturbing a running system. 37 | The Python API offers some convenience functions like 38 | `start_session_bus()` and `start_system_bus()` for this, in a 39 | `DBusTestCase` class (subclass of the standard `unittest.TestCase`) or 40 | alternatively as a `@pytest.fixture`. 41 | 42 | You can use this with any programming language, as you can run the 43 | mocker as a normal program. The actual setup of the mock (adding 44 | objects, methods, properties, and signals) all happen via D-Bus methods 45 | on the `org.freedesktop.DBus.Mock` interface. You just don't have the 46 | convenience D-Bus launch API that way. 47 | 48 | ## Simple example using Python's unittest 49 | 50 | Picking up the above example about mocking upower's `Suspend()` method, 51 | this is how you would set up a mock upower in your test case: 52 | 53 | ```python 54 | import subprocess 55 | 56 | import dbus 57 | 58 | import dbusmock 59 | 60 | 61 | class TestMyProgram(dbusmock.DBusTestCase): 62 | @classmethod 63 | def setUpClass(cls): 64 | cls.start_system_bus() 65 | cls.dbus_con = cls.get_dbus(system_bus=True) 66 | 67 | def setUp(self): 68 | self.p_mock = self.spawn_server('org.freedesktop.UPower', 69 | '/org/freedesktop/UPower', 70 | 'org.freedesktop.UPower', 71 | system_bus=True, 72 | stdout=subprocess.PIPE) 73 | 74 | # Get a proxy for the UPower object's Mock interface 75 | self.dbus_upower_mock = dbus.Interface(self.dbus_con.get_object( 76 | 'org.freedesktop.UPower', '/org/freedesktop/UPower'), 77 | dbusmock.MOCK_IFACE) 78 | 79 | self.dbus_upower_mock.AddMethod('', 'Suspend', '', '', '') 80 | 81 | def tearDown(self): 82 | self.p_mock.stdout.close() 83 | self.p_mock.terminate() 84 | self.p_mock.wait() 85 | 86 | def test_suspend_on_idle(self): 87 | # run your program in a way that should trigger one suspend call 88 | 89 | # now check the log that we got one Suspend() call 90 | self.assertRegex(self.p_mock.stdout.readline(), b'^[0-9.]+ Suspend$') 91 | ``` 92 | 93 | Let's walk through: 94 | 95 | - We derive our tests from `dbusmock.DBusTestCase` instead of 96 | `unittest.TestCase` directly, to make use of the convenience API 97 | to start a local system bus. 98 | 99 | - `setUpClass()` starts a local system bus, and makes a connection 100 | to it available to all methods as `dbus_con`. `True` means that we 101 | connect to the system bus, not the session bus. We can use the 102 | same bus for all tests, so doing this once in `setUpClass()` 103 | instead of `setUp()` is enough. 104 | 105 | - `setUp()` spawns the mock D-Bus server process for an initial 106 | `/org/freedesktop/UPower` object with an `org.freedesktop.UPower` 107 | D-Bus interface on the system bus. We capture its stdout to be 108 | able to verify that methods were called. 109 | 110 | We then call `org.freedesktop.DBus.Mock.AddMethod()` to add a 111 | `Suspend()` method to our new object to the default D-Bus 112 | interface. This will not do anything (except log its call to 113 | stdout). It takes no input arguments, returns nothing, and does 114 | not run any custom code. 115 | 116 | - `tearDown()` stops our mock D-Bus server again. We do this so that 117 | each test case has a fresh and clean upower instance, but of 118 | course you can also set up everything in `setUpClass()` if tests 119 | do not interfere with each other on setting up the mock. 120 | 121 | - `test_suspend_on_idle()` is the actual test case. It needs to run 122 | your program in a way that should trigger one suspend call. Your 123 | program will try to call `Suspend()`, but as that's now being 124 | served by our mock instead of upower, there will not be any actual 125 | machine suspend. Our mock process will log the method call 126 | together with a time stamp; you can use the latter for doing 127 | timing related tests, but we just ignore it here. 128 | 129 | ## Simple example using pytest 130 | 131 | The same functionality as above but instead using the pytest fixture provided 132 | by this package. 133 | 134 | ```python 135 | import subprocess 136 | 137 | import dbus 138 | import pytest 139 | 140 | import dbusmock 141 | 142 | 143 | @pytest.fixture 144 | def upower_mock(dbusmock_system): 145 | p_mock = dbusmock_system.spawn_server( 146 | 'org.freedesktop.UPower', 147 | '/org/freedesktop/UPower', 148 | 'org.freedesktop.UPower', 149 | system_bus=True, 150 | stdout=subprocess.PIPE) 151 | 152 | # Get a proxy for the UPower object's Mock interface 153 | dbus_upower_mock = dbus.Interface(dbusmock_system.get_dbus(True).get_object( 154 | 'org.freedesktop.UPower', 155 | '/org/freedesktop/UPower' 156 | ), dbusmock.MOCK_IFACE) 157 | dbus_upower_mock.AddMethod('', 'Suspend', '', '', '') 158 | 159 | yield p_mock 160 | 161 | p_mock.stdout.close() 162 | p_mock.terminate() 163 | p_mock.wait() 164 | 165 | 166 | def test_suspend_on_idle(upower_mock): 167 | # run your program in a way that should trigger one suspend call 168 | 169 | # now check the log that we got one Suspend() call 170 | assert upower_mock.stdout.readline() == b'^[0-9.]+ Suspend$' 171 | ``` 172 | 173 | Let's walk through: 174 | 175 | - We import the `dbusmock_system` fixture from dbusmock which provides us 176 | with a system bus started for our test case wherever the 177 | `dbusmock_system` argument is used by a test case and/or a pytest 178 | fixture. 179 | 180 | - The `upower_mock` fixture spawns the mock D-Bus server process for an initial 181 | `/org/freedesktop/UPower` object with an `org.freedesktop.UPower` 182 | D-Bus interface on the system bus. We capture its stdout to be 183 | able to verify that methods were called. 184 | 185 | We then call `org.freedesktop.DBus.Mock.AddMethod()` to add a 186 | `Suspend()` method to our new object to the default D-Bus 187 | interface. This will not do anything (except log its call to 188 | stdout). It takes no input arguments, returns nothing, and does 189 | not run any custom code. 190 | 191 | This mock server process is yielded to the test function that uses 192 | the `upower_mock` fixture - once the test is complete the process is 193 | terminated again. 194 | 195 | - `test_suspend_on_idle()` is the actual test case. It needs to run 196 | your program in a way that should trigger one suspend call. Your 197 | program will try to call `Suspend()`, but as that's now being 198 | served by our mock instead of upower, there will not be any actual 199 | machine suspend. Our mock process will log the method call 200 | together with a time stamp; you can use the latter for doing 201 | timing related tests, but we just ignore it here. 202 | 203 | ## Simple example from shell 204 | 205 | We use the actual session bus for this example. You can use 206 | `dbus-run-session` to start a private one as well if you want, but that 207 | is not part of the actual mocking. 208 | 209 | So let's start a mock at the D-Bus name `com.example.Foo` with an 210 | initial "main" object on path /, with the main D-Bus interface 211 | `com.example.Foo.Manager`: 212 | 213 | python3 -m dbusmock com.example.Foo / com.example.Foo.Manager 214 | 215 | On another terminal, let's first see what it does: 216 | 217 | gdbus introspect --session -d com.example.Foo -o / 218 | 219 | You'll see that it supports the standard D-Bus `Introspectable` and 220 | `Properties` interfaces, as well as the `org.freedesktop.DBus.Mock` 221 | interface for controlling the mock, but no "real" functionality yet. 222 | So let's add a method: 223 | 224 | gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod '' Ping '' '' '' 225 | 226 | Now you can see the new method in `introspect`, and call it: 227 | 228 | gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Ping 229 | 230 | The mock process in the other terminal will log the method call with a 231 | time stamp, and you'll see something like `1348832614.970 Ping`. 232 | 233 | Now add another method with two int arguments and a return value and 234 | call it: 235 | 236 | gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod \ 237 | '' Add 'ii' 'i' 'ret = args[0] + args[1]' 238 | gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Add 2 3 239 | 240 | This will print `(5,)` as expected (remember that the return value is 241 | always a tuple), and again the mock process will log the Add method 242 | call. 243 | 244 | You can do the same operations in e. g. d-feet or any other D-Bus 245 | language binding. 246 | 247 | ## Interactive debugging 248 | 249 | It's possible to use dbus-mock to run interactive sessions using something like: 250 | 251 | python3 -m dbusmock com.example.Foo / com.example.Foo.Manager -e $SHELL 252 | 253 | Where a shell session with the defined mocks is set and others can be added. 254 | 255 | Or more complex ones such as: 256 | 257 | python3 -m dbusmock --session -t upower -e \ 258 | python3 -m dbusmock com.example.Foo / com.example.Foo.Manager -e \ 259 | gdbus introspect --session -d com.example.Foo -o / 260 | 261 | ## Logging 262 | 263 | Usually you want to verify which methods have been called on the mock 264 | with which arguments. There are three ways to do that: 265 | 266 | - By default, the mock process writes the call log to stdout. 267 | - You can call the mock process with the `-l`/`--logfile` argument, 268 | or specify a log file object in the `spawn_server()` method if you 269 | are using Python. 270 | - You can use the `GetCalls()`, `GetMethodCalls()` and 271 | `ClearCalls()` methods on the `org.freedesktop.DBus.Mock` D-Bus 272 | interface to get an array of tuples describing the calls. 273 | 274 | ## Templates 275 | 276 | Some D-Bus services are commonly used in test suites, such as UPower or 277 | NetworkManager. python-dbusmock provides "templates" which set up the 278 | common structure of these services (their main objects, properties, and 279 | methods) so that you do not need to carry around this common code, and 280 | only need to set up the particular properties and specific D-Bus objects 281 | that you need. These templates can be parameterized for common 282 | customizations, and they can provide additional convenience methods on 283 | the `org.freedesktop.DBus.Mock` interface to provide more abstract 284 | functionality like "add a battery". 285 | 286 | For example, for starting a server with the `upower` template in 287 | Python you can run 288 | 289 | (self.p_mock, self.obj_upower) = self.spawn_server_template( 290 | 'upower', {'OnBattery': True}, stdout=subprocess.PIPE) 291 | 292 | or load a template into an already running server with the 293 | `AddTemplate()` method; this is particularly useful if you are not using 294 | Python: 295 | 296 | python3 -m dbusmock --system org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower 297 | 298 | gdbus call --system -d org.freedesktop.UPower -o /org/freedesktop/UPower -m org.freedesktop.DBus.Mock.AddTemplate 'upower' '{"OnBattery": }' 299 | 300 | This creates all expected properties such as `DaemonVersion`, and 301 | changes the default for one of them (`OnBattery`) through the (optional) 302 | parameters dict. 303 | 304 | If you do not need to specify parameters, you can do this in a simpler 305 | way with 306 | 307 | python3 -m dbusmock --template upower 308 | 309 | The template does not create any devices by default. You can add some 310 | with the template's convenience methods like 311 | 312 | ac_path = self.dbusmock.AddAC('mock_AC', 'Mock AC') 313 | bt_path = self.dbusmock.AddChargingBattery('mock_BAT', 'Mock Battery', 30.0, 1200) 314 | 315 | or calling `AddObject()` yourself with the desired properties, of 316 | course. 317 | 318 | Templates commonly implement some non-trivial functionality with actual Python 319 | methods and the standard [dbus-python](https://dbus.freedesktop.org/doc/dbus-python/) 320 | [`@dbus.service.method`](https://dbus.freedesktop.org/doc/dbus-python/dbus.service.html#dbus.service.method) 321 | decorator. 322 | 323 | To build your own template, you can copy 324 | [dbusmock/templates/SKELETON](./dbusmock/templates/SKELETON) to your 325 | new template file name and replace `CHANGEME` with the actual code/values. 326 | Look at [dbusmock/templates/upower.py](./dbusmock/templates/upower.py) for 327 | a real-life implementation. 328 | 329 | A template can be loaded from these locations: 330 | 331 | * Provide a path to its `.py` file. This is intended for running tests out of 332 | git/build trees with very project specific or unstable templates. 333 | 334 | * From [`$XDG_DATA_DIRS/python-dbusmock/templates/`*name*`.py`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). 335 | This is intended for shipping reusable templates in distribution development 336 | packages. Load them by module name. 337 | 338 | * python-dbusmock [ships a set of widely applicable templates](./dbusmock/templates/) 339 | which are collaboratively maintained, like the `upower` one in the example 340 | above. Load them by module name. 341 | 342 | ## More Examples 343 | 344 | Have a look at the test suite for two real-live use cases: 345 | 346 | - `tests/test_upower.py` simulates upowerd, in a more complete way 347 | than in above example and using the `upower` template. It verifies 348 | that `upower --dump` is convinced that it's talking to upower. 349 | - `tests/test_api.py` runs a mock on the session bus and exercises 350 | all available functionality, such as adding additional objects, 351 | properties, multiple methods, input arguments, return values, code 352 | in methods, sending signals, raising exceptions, and introspection. 353 | 354 | ## Documentation 355 | 356 | The `dbusmock` module has extensive documentation built in, which you 357 | can read with e. g. `pydoc3 dbusmock` or online at 358 | https://martinpitt.github.io/python-dbusmock/ 359 | 360 | `pydoc3 dbusmock.DBusMockObject` shows the D-Bus API of the mock object, 361 | i. e. methods like `AddObject()`, `AddMethod()` etc. which are used to 362 | set up your mock object. 363 | 364 | `pydoc3 dbusmock.DBusTestCase` shows the convenience Python API for 365 | writing test cases with local private session/system buses and launching 366 | the server. 367 | 368 | `pydoc3 dbusmock.templates` shows all available templates. 369 | 370 | `pydoc3 dbusmock.templates.NAME` shows the documentation and available 371 | parameters for the `NAME` template. 372 | 373 | `python3 -m dbusmock --help` shows the arguments and options for running 374 | the mock server as a program. 375 | 376 | ## Development 377 | 378 | python-dbusmock is hosted on https://github.com/martinpitt/python-dbusmock 379 | 380 | Run the unit tests with `python3 -m unittest` or `pytest`. 381 | 382 | In CI, the unit tests run in containers. You can run them locally with e.g. 383 | 384 | tests/run registry.fedoraproject.org/fedora:latest 385 | 386 | Check the [unit-tests GitHub workflow](.github/workflows/tests.yml) for the 387 | operating systems/container images on which python-dbusmock is tested and 388 | supported. 389 | 390 | To debug failures interactively, run 391 | 392 | DEBUG=1 tests/run [image] 393 | 394 | which will sleep on failures. You can then attach to the running container 395 | image with e.g. `podman exec -itl bash`. The `/source` directory is mounted from the 396 | host, i.e. edit files in your normal git checkout outside of the container, and 397 | re-run all tests in the container shell like above. You can also run a specific 398 | test: 399 | 400 | python3 -m unittest tests.test_api.TestAPI.test_onearg_ret 401 | -------------------------------------------------------------------------------- /dbusmock/__init__.py: -------------------------------------------------------------------------------- 1 | """Mock D-Bus objects for test suites.""" 2 | 3 | # This program is free software; you can redistribute it and/or modify it under 4 | # the terms of the GNU Lesser General Public License as published by the Free 5 | # Software Foundation; either version 3 of the License, or (at your option) any 6 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 7 | # of the license. 8 | 9 | __author__ = "Martin Pitt" 10 | __copyright__ = """ 11 | (c) 2012 Canonical Ltd. 12 | (c) 2017 - 2022 Martin Pitt 13 | """ 14 | 15 | 16 | from dbusmock.mockobject import MOCK_IFACE, OBJECT_MANAGER_IFACE, DBusMockObject, get_object, get_objects 17 | from dbusmock.testcase import BusType, DBusTestCase, PrivateDBus, SpawnedMock 18 | 19 | try: 20 | # created by setuptools_scm 21 | from dbusmock._version import __version__ 22 | except ImportError: 23 | __version__ = "0.git" 24 | 25 | 26 | __all__ = [ 27 | "MOCK_IFACE", 28 | "OBJECT_MANAGER_IFACE", 29 | "BusType", 30 | "DBusMockObject", 31 | "DBusTestCase", 32 | "PrivateDBus", 33 | "SpawnedMock", 34 | "get_object", 35 | "get_objects", 36 | ] 37 | -------------------------------------------------------------------------------- /dbusmock/__main__.py: -------------------------------------------------------------------------------- 1 | """Main entry point for running mock server.""" 2 | 3 | # This program is free software; you can redistribute it and/or modify it under 4 | # the terms of the GNU Lesser General Public License as published by the Free 5 | # Software Foundation; either version 3 of the License, or (at your option) any 6 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 7 | # of the license. 8 | 9 | __author__ = "Martin Pitt" 10 | __copyright__ = """ 11 | (c) 2012 Canonical Ltd. 12 | (c) 2017 - 2022 Martin Pitt 13 | """ 14 | 15 | import argparse 16 | import json 17 | import os 18 | import platform 19 | import subprocess 20 | import sys 21 | 22 | import dbusmock.mockobject 23 | import dbusmock.testcase 24 | 25 | 26 | def parse_args(): 27 | """Parse command line arguments""" 28 | 29 | parser = argparse.ArgumentParser(description="mock D-Bus object") 30 | parser.add_argument( 31 | "-s", 32 | "--system", 33 | action="store_true", 34 | help="put object(s) on system bus (default: session bus or template's SYSTEM_BUS flag)", 35 | ) 36 | parser.add_argument( 37 | "--session", 38 | action="store_true", 39 | help="put object(s) on session bus (default without template; overrides template's SYSTEM_BUS flag)", 40 | ) 41 | parser.add_argument( 42 | "-l", 43 | "--logfile", 44 | metavar="PATH", 45 | help="path of log file", 46 | ) 47 | parser.add_argument( 48 | "-t", 49 | "--template", 50 | metavar="NAME", 51 | help="template to load (instead of specifying name, path, interface)", 52 | ) 53 | parser.add_argument( 54 | "name", 55 | metavar="NAME", 56 | nargs="?", 57 | help='D-Bus name to claim (e. g. "com.example.MyService") (if not using -t)', 58 | ) 59 | parser.add_argument( 60 | "path", 61 | metavar="PATH", 62 | nargs="?", 63 | help="D-Bus object path for initial/main object (if not using -t)", 64 | ) 65 | parser.add_argument( 66 | "interface", 67 | metavar="INTERFACE", 68 | nargs="?", 69 | help="main D-Bus interface name for initial object (if not using -t)", 70 | ) 71 | parser.add_argument( 72 | "-m", 73 | "--is-object-manager", 74 | action="store_true", 75 | help="automatically implement the org.freedesktop.DBus.ObjectManager interface", 76 | ) 77 | parser.add_argument( 78 | "-p", 79 | "--parameters", 80 | help="JSON dictionary of parameters to pass to the template", 81 | ) 82 | parser.add_argument( 83 | "-e", 84 | "--exec", 85 | nargs=argparse.REMAINDER, 86 | help="Command to run in the mock environment", 87 | ) 88 | 89 | arguments = parser.parse_args() 90 | 91 | if arguments.template: 92 | if arguments.name or arguments.path or arguments.interface: 93 | parser.error("--template and specifying NAME/PATH/INTERFACE are mutually exclusive") 94 | else: 95 | if not arguments.name or not arguments.path or not arguments.interface: 96 | parser.error("Not using a template, you must specify NAME, PATH, and INTERFACE") 97 | 98 | if arguments.system and arguments.session: 99 | parser.error("--system and --session are mutually exclusive") 100 | 101 | return arguments 102 | 103 | 104 | if __name__ == "__main__": 105 | import ctypes 106 | 107 | import dbus.mainloop.glib 108 | import dbus.service 109 | 110 | args = parse_args() 111 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 112 | 113 | system_bus = args.system 114 | if args.template: 115 | module = dbusmock.mockobject.load_module(args.template) 116 | args.name = module.BUS_NAME 117 | args.path = module.MAIN_OBJ 118 | if not args.session and not args.system: 119 | system_bus = module.SYSTEM_BUS 120 | 121 | if hasattr(module, "IS_OBJECT_MANAGER"): 122 | args.is_object_manager = module.IS_OBJECT_MANAGER 123 | else: 124 | args.is_object_manager = False 125 | 126 | if args.is_object_manager and not hasattr(module, "MAIN_IFACE"): 127 | args.interface = dbusmock.mockobject.OBJECT_MANAGER_IFACE 128 | else: 129 | args.interface = module.MAIN_IFACE 130 | 131 | bus = dbusmock.testcase.DBusTestCase.get_dbus(system_bus) 132 | 133 | # quit mock when the bus is going down 134 | should_run = {True} 135 | bus.add_signal_receiver( 136 | should_run.pop, 137 | signal_name="Disconnected", 138 | path="/org/freedesktop/DBus/Local", 139 | dbus_interface="org.freedesktop.DBus.Local", 140 | ) 141 | 142 | bus_name = dbus.service.BusName(args.name, bus, allow_replacement=True, replace_existing=True, do_not_queue=True) 143 | 144 | main_object = dbusmock.mockobject.DBusMockObject( 145 | bus_name, args.path, args.interface, {}, args.logfile, args.is_object_manager 146 | ) 147 | 148 | parameters = None 149 | if args.parameters: 150 | try: 151 | parameters = json.loads(args.parameters) 152 | except ValueError as detail: 153 | sys.stderr.write(f"Malformed JSON given for parameters: {detail}\n") 154 | sys.exit(2) 155 | 156 | if not isinstance(parameters, dict): 157 | sys.stderr.write("JSON parameters must be a dictionary\n") 158 | sys.exit(2) 159 | 160 | if args.template: 161 | main_object.AddTemplate(args.template, parameters) 162 | 163 | if platform.system() == "Darwin": 164 | libglib = ctypes.cdll.LoadLibrary("libglib-2.0.dylib") 165 | else: 166 | libglib = ctypes.cdll.LoadLibrary("libglib-2.0.so.0") 167 | 168 | dbusmock.mockobject.objects[args.path] = main_object 169 | 170 | if args.exec: 171 | with subprocess.Popen(args.exec) as exec_proc: 172 | exit_status = set() 173 | 174 | @ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_int) 175 | def on_process_watch(_pid, status): 176 | """Check if the launched process is still alive""" 177 | if os.WIFEXITED(status): 178 | exit_status.add(os.WEXITSTATUS(status)) 179 | else: 180 | exit_status.add(1) 181 | should_run.pop() 182 | 183 | libglib.g_child_watch_add(exec_proc.pid, on_process_watch) 184 | 185 | while should_run: 186 | libglib.g_main_context_iteration(None, True) 187 | 188 | try: 189 | exec_proc.terminate() 190 | exec_proc.wait() 191 | except ProcessLookupError: 192 | pass 193 | sys.exit(exit_status.pop() if exit_status else exec_proc.returncode) 194 | else: 195 | while should_run: 196 | libglib.g_main_context_iteration(None, True) 197 | -------------------------------------------------------------------------------- /dbusmock/pytest_fixtures.py: -------------------------------------------------------------------------------- 1 | """pytest fixtures for DBusMock""" 2 | 3 | # This program is free software; you can redistribute it and/or modify it under 4 | # the terms of the GNU Lesser General Public License as published by the Free 5 | # Software Foundation; either version 3 of the License, or (at your option) any 6 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 7 | # of the license. 8 | 9 | __author__ = "Martin Pitt" 10 | __copyright__ = "(c) 2023 Martin Pitt " 11 | 12 | from typing import Iterator 13 | 14 | import pytest 15 | 16 | from dbusmock.testcase import BusType, PrivateDBus 17 | 18 | 19 | @pytest.fixture(scope="session") 20 | def dbusmock_system() -> Iterator[PrivateDBus]: 21 | """Export the whole DBusTestCase as a fixture, with the system bus started""" 22 | 23 | with PrivateDBus(BusType.SYSTEM) as bus: 24 | yield bus 25 | 26 | 27 | @pytest.fixture(scope="session") 28 | def dbusmock_session() -> Iterator[PrivateDBus]: 29 | """Export the whole DBusTestCase as a fixture, with the session bus started""" 30 | 31 | with PrivateDBus(BusType.SESSION) as bus: 32 | yield bus 33 | -------------------------------------------------------------------------------- /dbusmock/templates/SKELETON: -------------------------------------------------------------------------------- 1 | '''CHANGEME mock template 2 | 3 | This creates the expected methods and properties of the main 4 | CHANGEME object, but no devices. You can specify any property 5 | such as CHANGEME in "parameters". 6 | ''' 7 | 8 | # This program is free software; you can redistribute it and/or modify it under 9 | # the terms of the GNU Lesser General Public License as published by the Free 10 | # Software Foundation; either version 3 of the License, or (at your option) any 11 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 12 | # of the license. 13 | 14 | __author__ = 'CHANGEME' 15 | __copyright__ = 'CHANGEME' 16 | 17 | import dbus 18 | 19 | from dbusmock import MOCK_IFACE 20 | 21 | # False for session bus, True for system bus; if not present, the bus has to be 22 | # specified in the spawn_server_template() call 23 | SYSTEM_BUS = True # CHANGEME 24 | BUS_NAME = 'org.freedesktop.CHANGEME' 25 | MAIN_OBJ = '/org/freedesktop/CHANGEME' 26 | # If your top-level object is an org.freedesktop.DBus.ObjectManager, you can 27 | # skip setting MAIN_IFACE and set IS_OBJECT_MANAGER to True; then dbusmock will 28 | # automatically provide the GetManagedObjects() API. In all other cases, 29 | # specify the interface name of the main object here. 30 | MAIN_IFACE = 'org.freedesktop.CHANGEME' 31 | # IS_OBJECT_MANAGER = True 32 | 33 | 34 | def load(mock, parameters): 35 | mock.AddMethods(MAIN_IFACE, [ 36 | # CHANGEME: Add some methods if required, otherwise drop the AddMethods call 37 | ('CHANGEME', '', 'b', 'ret = %s' % parameters.get('CHANGEME', True)), 38 | ]) 39 | 40 | mock.AddProperties(MAIN_IFACE, 41 | dbus.Dictionary({ 42 | # CHANGEME: Add properties if required, otherwise 43 | # drop this call 44 | 'MyProperty': parameters.get('MyProperty', 'CHANGEME'), 45 | }, signature='sv')) 46 | 47 | 48 | # CHANGEME: You can add convenience methods to the org.freedesktop.DBus.Mock 49 | # interface to provide abstract functionality such as adding specific devices 50 | 51 | @dbus.service.method(MOCK_IFACE, 52 | in_signature='ss', out_signature='s') 53 | def AddCHANGEME(self, device_name, _CHANGEME): 54 | '''Convenience method to add a CHANGEME object 55 | 56 | You have to specify a ... 57 | 58 | Please note that this does not set any global properties. 59 | 60 | Returns the new object path. 61 | ''' 62 | path = '/org/freedesktop/CHANGEME/' + device_name 63 | self.AddObject(path, ...) 64 | return path 65 | -------------------------------------------------------------------------------- /dbusmock/templates/__init__.py: -------------------------------------------------------------------------------- 1 | """Mock templates for common D-Bus services""" 2 | 3 | # This program is free software; you can redistribute it and/or modify it under 4 | # the terms of the GNU Lesser General Public License as published by the Free 5 | # Software Foundation; either version 3 of the License, or (at your option) any 6 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 7 | # of the license. 8 | -------------------------------------------------------------------------------- /dbusmock/templates/bluez5-obex.py: -------------------------------------------------------------------------------- 1 | """obexd mock template 2 | 3 | This creates the expected methods and properties of the object manager 4 | org.bluez.obex object (/), the manager object (/org/bluez/obex), but no agents 5 | or clients. 6 | 7 | This supports BlueZ 5 only. 8 | """ 9 | 10 | # This program is free software; you can redistribute it and/or modify it under 11 | # the terms of the GNU Lesser General Public License as published by the Free 12 | # Software Foundation; either version 3 of the License, or (at your option) any 13 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 14 | # of the license. 15 | 16 | __author__ = "Philip Withnall" 17 | __copyright__ = """ 18 | (c) 2013 Collabora Ltd. 19 | (c) 2017 - 2022 Martin Pitt 20 | """ 21 | 22 | import tempfile 23 | from pathlib import Path 24 | 25 | import dbus 26 | 27 | from dbusmock import OBJECT_MANAGER_IFACE, mockobject 28 | 29 | BUS_NAME = "org.bluez.obex" 30 | MAIN_OBJ = "/" 31 | SYSTEM_BUS = False 32 | IS_OBJECT_MANAGER = True 33 | 34 | OBEX_MOCK_IFACE = "org.bluez.obex.Mock" 35 | AGENT_MANAGER_IFACE = "org.bluez.AgentManager1" 36 | CLIENT_IFACE = "org.bluez.obex.Client1" 37 | SESSION_IFACE = "org.bluez.obex.Session1" 38 | PHONEBOOK_ACCESS_IFACE = "org.bluez.obex.PhonebookAccess1" 39 | TRANSFER_IFACE = "org.bluez.obex.Transfer1" 40 | TRANSFER_MOCK_IFACE = "org.bluez.obex.transfer1.Mock" 41 | 42 | 43 | def load(mock, _parameters): 44 | mock.AddObject( 45 | "/org/bluez/obex", 46 | AGENT_MANAGER_IFACE, 47 | {}, 48 | [ 49 | ("RegisterAgent", "os", "", ""), 50 | ("UnregisterAgent", "o", "", ""), 51 | ], 52 | ) 53 | 54 | obex = mockobject.objects["/org/bluez/obex"] 55 | obex.AddMethods( 56 | CLIENT_IFACE, 57 | [ 58 | ("CreateSession", "sa{sv}", "o", CreateSession), 59 | ("RemoveSession", "o", "", RemoveSession), 60 | ], 61 | ) 62 | 63 | 64 | @dbus.service.method(CLIENT_IFACE, in_signature="sa{sv}", out_signature="o") 65 | def CreateSession(self, destination, args): 66 | """OBEX method to create a new transfer session. 67 | 68 | The destination must be the address of the destination Bluetooth device. 69 | The given arguments must be a map from well-known keys to values, 70 | containing at least the `Target` key, whose value must be `PBAP` (other 71 | keys and values are accepted by the real daemon, but not by this mock 72 | daemon at the moment). If the target is missing or incorrect, an 73 | Unsupported error is returned on the bus. 74 | 75 | Returns the path of a new Session object. 76 | """ 77 | 78 | if "Target" not in args or args["Target"].upper() != "PBAP": 79 | raise dbus.exceptions.DBusException( 80 | "Non-PBAP targets are not currently supported by this python-dbusmock template.", 81 | name=OBEX_MOCK_IFACE + ".Unsupported", 82 | ) 83 | 84 | # Find the first unused session ID. 85 | client_path = "/org/bluez/obex/client" 86 | session_id = 0 87 | while client_path + "/session" + str(session_id) in mockobject.objects: 88 | session_id += 1 89 | 90 | path = client_path + "/session" + str(session_id) 91 | properties = { 92 | "Source": dbus.String("FIXME"), 93 | "Destination": dbus.String(destination), 94 | "Channel": dbus.Byte(0), 95 | "Target": dbus.String("FIXME"), 96 | "Root": dbus.String("FIXME"), 97 | } 98 | 99 | self.AddObject( 100 | path, 101 | SESSION_IFACE, 102 | # Properties 103 | properties, 104 | # Methods 105 | [ 106 | ("GetCapabilities", "", "s", ""), # Currently a no-op 107 | ], 108 | ) 109 | 110 | session = mockobject.objects[path] 111 | session.AddMethods( 112 | PHONEBOOK_ACCESS_IFACE, 113 | [ 114 | ("Select", "ss", "", ""), # Currently a no-op 115 | # Currently a no-op 116 | ("List", "a{sv}", "a(ss)", 'ret = dbus.Array(signature="(ss)")'), 117 | # Currently a no-op 118 | ("ListFilterFields", "", "as", 'ret = dbus.Array(signature="(s)")'), 119 | ("PullAll", "sa{sv}", "sa{sv}", PullAll), 120 | ("GetSize", "", "q", "ret = 0"), 121 | ], 122 | ) 123 | 124 | manager = mockobject.objects["/"] 125 | manager.EmitSignal( 126 | OBJECT_MANAGER_IFACE, 127 | "InterfacesAdded", 128 | "oa{sa{sv}}", 129 | [ 130 | dbus.ObjectPath(path), 131 | {SESSION_IFACE: properties}, 132 | ], 133 | ) 134 | 135 | return path 136 | 137 | 138 | @dbus.service.method(CLIENT_IFACE, in_signature="o", out_signature="") 139 | def RemoveSession(self, session_path): 140 | """OBEX method to remove an existing transfer session. 141 | 142 | This takes the path of the transfer Session object and removes it. 143 | """ 144 | 145 | manager = mockobject.objects["/"] 146 | 147 | # Remove all the session's transfers. 148 | transfer_id = 0 149 | while session_path + "/transfer" + str(transfer_id) in mockobject.objects: 150 | transfer_path = session_path + "/transfer" + str(transfer_id) 151 | transfer_id += 1 152 | 153 | self.RemoveObject(transfer_path) 154 | 155 | manager.EmitSignal( 156 | OBJECT_MANAGER_IFACE, 157 | "InterfacesRemoved", 158 | "oas", 159 | [ 160 | dbus.ObjectPath(transfer_path), 161 | [TRANSFER_IFACE], 162 | ], 163 | ) 164 | 165 | # Remove the session itself. 166 | self.RemoveObject(session_path) 167 | 168 | manager.EmitSignal( 169 | OBJECT_MANAGER_IFACE, 170 | "InterfacesRemoved", 171 | "oas", 172 | [ 173 | dbus.ObjectPath(session_path), 174 | [SESSION_IFACE, PHONEBOOK_ACCESS_IFACE], 175 | ], 176 | ) 177 | 178 | 179 | @dbus.service.method(PHONEBOOK_ACCESS_IFACE, in_signature="sa{sv}", out_signature="sa{sv}") 180 | def PullAll(self, target_file, filters): 181 | """OBEX method to start a pull transfer of a phone book. 182 | 183 | This doesn't complete the transfer; code to mock up activating and 184 | completing the transfer must be provided by the test driver, as it is 185 | too complex and test-specific to put here. 186 | 187 | The target_file is the absolute path for a file which will have zero or 188 | more vCards, separated by new-line characters, written to it if the method 189 | is successful (and the transfer is completed). This target_file is actually 190 | emitted in a TransferCreated signal, which is a special part of the mock 191 | interface designed to be handled by the test driver, which should then 192 | populate that file and call UpdateStatus on the Transfer object. The test 193 | driver is responsible for deleting the file once the test is complete. 194 | 195 | The filters parameter is a map of filters to be applied to the results 196 | device-side before transmitting them back to the adapter. 197 | 198 | Returns a tuple containing the path for a new Transfer D-Bus object 199 | representing the transfer, and a map of the initial properties of that 200 | Transfer object. 201 | """ 202 | 203 | # Find the first unused session ID. 204 | session_path = self.path 205 | transfer_id = 0 206 | while session_path + "/transfer" + str(transfer_id) in mockobject.objects: 207 | transfer_id += 1 208 | 209 | transfer_path = session_path + "/transfer" + str(transfer_id) 210 | 211 | # Create a new temporary file to transfer to. 212 | with tempfile.NamedTemporaryFile(suffix=".vcf", prefix="tmp-bluez5-obex-PullAll_", delete=False) as temp_file: 213 | filename = Path(temp_file.name).resolve() 214 | 215 | props = { 216 | "Status": dbus.String("queued"), 217 | "Session": dbus.ObjectPath(session_path), 218 | "Name": dbus.String(target_file), 219 | "Filename": dbus.String(filename), 220 | "Transferred": dbus.UInt64(0), 221 | } 222 | 223 | self.AddObject( 224 | transfer_path, 225 | TRANSFER_IFACE, 226 | # Properties 227 | props, 228 | # Methods 229 | [ 230 | ("Cancel", "", "", ""), # Currently a no-op 231 | ], 232 | ) 233 | 234 | transfer = mockobject.objects[transfer_path] 235 | transfer.AddMethods( 236 | TRANSFER_MOCK_IFACE, 237 | [ 238 | ("UpdateStatus", "b", "", UpdateStatus), 239 | ], 240 | ) 241 | 242 | manager = mockobject.objects["/"] 243 | manager.EmitSignal( 244 | OBJECT_MANAGER_IFACE, 245 | "InterfacesAdded", 246 | "oa{sa{sv}}", 247 | [ 248 | dbus.ObjectPath(transfer_path), 249 | {TRANSFER_IFACE: props}, 250 | ], 251 | ) 252 | 253 | # Emit a behind-the-scenes signal that a new transfer has been created. 254 | manager.EmitSignal(OBEX_MOCK_IFACE, "TransferCreated", "sa{sv}s", [transfer_path, filters, str(filename)]) 255 | 256 | return (transfer_path, props) 257 | 258 | 259 | @dbus.service.signal(OBEX_MOCK_IFACE, signature="sa{sv}s") 260 | def TransferCreated(_self, _path, _filters, _transfer_filename): 261 | """Mock signal emitted when a new Transfer object is created. 262 | 263 | This is not part of the BlueZ OBEX interface; it is purely for use by test 264 | driver code. It is emitted by the PullAll method, and is intended to be 265 | used as a signal to call UpdateStatus on the newly created Transfer 266 | (potentially after a timeout). 267 | 268 | The path is of the new Transfer object, and the filters are as provided to 269 | PullAll. The transfer filename is the full path and filename of a newly 270 | created empty temporary file which the test driver should write to. 271 | 272 | The test driver should then call UpdateStatus on the Transfer object each 273 | time a write is made to the transfer file. The final call to UpdateStatus 274 | should mark the transfer as complete. 275 | 276 | The test driver is responsible for deleting the transfer file once the test 277 | is complete. 278 | 279 | FIXME: Ideally the UpdateStatus method would only be used for completion, 280 | and all intermediate updates would be found by watching the size of the 281 | transfer file. However, that means adding a dependency on an inotify 282 | package, which seems a little much. 283 | """ 284 | 285 | 286 | @dbus.service.method(TRANSFER_MOCK_IFACE, in_signature="b", out_signature="") 287 | def UpdateStatus(self, is_complete): 288 | """Mock method to update the transfer status. 289 | 290 | If is_complete is False, this marks the transfer is active; otherwise it 291 | marks the transfer as complete. It is an error to call this method after 292 | calling it with is_complete as True. 293 | 294 | In both cases, it updates the number of bytes transferred to be the current 295 | size of the transfer file (whose filename was emitted in the 296 | TransferCreated signal). 297 | """ 298 | status = "complete" if is_complete else "active" 299 | transferred = Path(self.props[TRANSFER_IFACE]["Filename"]).stat().st_size 300 | 301 | self.props[TRANSFER_IFACE]["Status"] = status 302 | self.props[TRANSFER_IFACE]["Transferred"] = dbus.UInt64(transferred) 303 | 304 | self.EmitSignal( 305 | dbus.PROPERTIES_IFACE, 306 | "PropertiesChanged", 307 | "sa{sv}as", 308 | [ 309 | TRANSFER_IFACE, 310 | { 311 | "Status": dbus.String(status), 312 | "Transferred": dbus.UInt64(transferred), 313 | }, 314 | [], 315 | ], 316 | ) 317 | -------------------------------------------------------------------------------- /dbusmock/templates/gnome_screensaver.py: -------------------------------------------------------------------------------- 1 | """gnome-shell screensaver mock template 2 | 3 | This creates the expected methods and properties of the 4 | org.gnome.ScreenSaver object. 5 | """ 6 | 7 | # This program is free software; you can redistribute it and/or modify it under 8 | # the terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 3 of the License, or (at your option) any 10 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 11 | # of the license. 12 | 13 | __author__ = "Bastien Nocera" 14 | __copyright__ = """ 15 | (c) 2013 Red Hat Inc. 16 | (c) 2017 - 2022 Martin Pitt 17 | """ 18 | 19 | BUS_NAME = "org.gnome.ScreenSaver" 20 | MAIN_OBJ = "/org/gnome/ScreenSaver" 21 | MAIN_IFACE = "org.gnome.ScreenSaver" 22 | SYSTEM_BUS = False 23 | 24 | 25 | def load(mock, _parameters): 26 | mock.AddMethods( 27 | MAIN_IFACE, 28 | [ 29 | ("GetActive", "", "b", "ret = self.is_active"), 30 | ("GetActiveTime", "", "u", "ret = 1"), 31 | ( 32 | "SetActive", 33 | "b", 34 | "", 35 | 'self.is_active = args[0]; self.EmitSignal("", "ActiveChanged", "b", [self.is_active])', 36 | ), 37 | ("Lock", "", "", "time.sleep(1); self.SetActive(True)"), 38 | ("ShowMessage", "sss", "", ""), 39 | ("SimulateUserActivity", "", "", ""), 40 | ], 41 | ) 42 | 43 | # default state 44 | mock.is_active = False 45 | -------------------------------------------------------------------------------- /dbusmock/templates/gsd_rfkill.py: -------------------------------------------------------------------------------- 1 | """gsd-rfkill mock template 2 | 3 | This creates the expected properties of the GNOME Settings Daemon's 4 | rfkill object. You can specify any property such as AirplaneMode in 5 | "parameters". 6 | """ 7 | 8 | # This program is free software; you can redistribute it and/or modify it under 9 | # the terms of the GNU Lesser General Public License as published by the Free 10 | # Software Foundation; either version 3 of the License, or (at your option) any 11 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 12 | # of the license. 13 | 14 | __author__ = "Guido Günther" 15 | __copyright__ = "2024 The Phosh Developers" 16 | 17 | import dbus 18 | 19 | from dbusmock import MOCK_IFACE 20 | 21 | SYSTEM_BUS = False 22 | BUS_NAME = "org.gnome.SettingsDaemon.Rfkill" 23 | MAIN_OBJ = "/org/gnome/SettingsDaemon/Rfkill" 24 | MAIN_IFACE = "org.gnome.SettingsDaemon.Rfkill" 25 | 26 | 27 | def load(mock, parameters): 28 | props = dbus.Dictionary( 29 | { 30 | "AirplaneMode": parameters.get("AirplaneMode", False), 31 | "BluetoothAirplaneMode": parameters.get("BluetoothAirplaneMode", False), 32 | "BluetoothHardwareAirplaneMode": parameters.get("BluetoothHardwareAirplaneMode", False), 33 | "BluetoothHasAirplaneMode": parameters.get("BluetoothHasAirplanemode", True), 34 | "HardwareAirplaneMode": parameters.get("HardwareAirplaneMode", False), 35 | "HasAirplaneMode": parameters.get("HasAirplaneMode", True), 36 | "ShouldShowAirplaneMode": parameters.get("ShouldShowAirplaneMode", True), 37 | "WwanAirplaneMode": parameters.get("WwanAirplaneMode", False), 38 | "WwanHardwareAirplaneMode": parameters.get("WwanHardwareAirplaneMode", False), 39 | "WwanHasAirplaneMode": parameters.get("WwanHasAirplaneMode", True), 40 | }, 41 | signature="sv", 42 | ) 43 | mock.AddProperties(MAIN_IFACE, props) 44 | 45 | 46 | @dbus.service.method(MOCK_IFACE, in_signature="b", out_signature="b") 47 | def SetAirplaneMode(self, mode): 48 | """ 49 | Convenience method to toggle airplane mode 50 | """ 51 | self.props[MAIN_IFACE]["AirplaneMode"] = mode 52 | self.props[MAIN_IFACE]["BluetoothAirplaneMode"] = mode 53 | self.props[MAIN_IFACE]["WwanAirplaneMode"] = mode 54 | return mode 55 | -------------------------------------------------------------------------------- /dbusmock/templates/iio-sensors-proxy.py: -------------------------------------------------------------------------------- 1 | """sensors proxy mock template 2 | """ 3 | 4 | # This program is free software; you can redistribute it and/or modify it under 5 | # the terms of the GNU Lesser General Public License as published by the Free 6 | # Software Foundation; either version 3 of the License, or (at your option) any 7 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 8 | # of the license. 9 | 10 | __author__ = "Marco Trevisan" 11 | __copyright__ = """ 12 | (c) 2021 Canonical Ltd. 13 | (c) 2017 - 2022 Martin Pitt 14 | """ 15 | 16 | import re 17 | 18 | import dbus 19 | 20 | from dbusmock import MOCK_IFACE 21 | 22 | BUS_NAME = "net.hadess.SensorProxy" 23 | MAIN_OBJ = "/net/hadess/SensorProxy" 24 | MAIN_IFACE = "net.hadess.SensorProxy" 25 | COMPASS_IFACE = "net.hadess.SensorProxy.Compass" 26 | SYSTEM_BUS = True 27 | 28 | CAMEL_TO_SNAKE_CASE_RE = re.compile(r"(? 18 | """ 19 | 20 | import os 21 | 22 | import dbus 23 | from gi.repository import GLib 24 | 25 | from dbusmock import MOCK_IFACE, mockobject 26 | 27 | BUS_NAME = "org.freedesktop.login1" 28 | MAIN_OBJ = "/org/freedesktop/login1" 29 | MAIN_IFACE = "org.freedesktop.login1.Manager" 30 | SYSTEM_BUS = True 31 | 32 | 33 | def load(mock, parameters): 34 | mock.AddMethods( 35 | MAIN_IFACE, 36 | [ 37 | ("PowerOff", "b", "", ""), 38 | ("Reboot", "b", "", ""), 39 | ("Suspend", "b", "", ""), 40 | ("Hibernate", "b", "", ""), 41 | ("HybridSleep", "b", "", ""), 42 | ("SuspendThenHibernate", "b", "", ""), 43 | ("CanPowerOff", "", "s", f'ret = "{parameters.get("CanPowerOff", "yes")}"'), 44 | ("CanReboot", "", "s", f'ret = "{parameters.get("CanReboot", "yes")}"'), 45 | ("CanSuspend", "", "s", f'ret = "{parameters.get("CanSuspend", "yes")}"'), 46 | ("CanHibernate", "", "s", f'ret = "{parameters.get("CanHibernate", "yes")}"'), 47 | ("CanHybridSleep", "", "s", f'ret = "{parameters.get("CanHybridSleep", "yes")}"'), 48 | ("CanSuspendThenHibernate", "", "s", f'ret = "{parameters.get("CanSuspendThenHibernate", "yes")}"'), 49 | ("GetSession", "s", "o", 'ret = "/org/freedesktop/login1/session/" + args[0]'), 50 | ("ActivateSession", "s", "", ""), 51 | ("ActivateSessionOnSeat", "ss", "", ""), 52 | ("KillSession", "sss", "", ""), 53 | ("LockSession", "s", "", ""), 54 | ("LockSessions", "", "", ""), 55 | ("ReleaseSession", "s", "", ""), 56 | ("TerminateSession", "s", "", ""), 57 | ("UnlockSession", "s", "", ""), 58 | ("UnlockSessions", "", "", ""), 59 | ("GetSeat", "s", "o", 'ret = "/org/freedesktop/login1/seat/" + args[0]'), 60 | ("ListSeats", "", "a(so)", 'ret = [(k.split("/")[-1], k) for k in objects.keys() if "/seat/" in k]'), 61 | ("TerminateSeat", "s", "", ""), 62 | ("GetUser", "u", "o", 'ret = "/org/freedesktop/login1/user/" + args[0]'), 63 | ("KillUser", "us", "", ""), 64 | ("TerminateUser", "u", "", ""), 65 | ], 66 | ) 67 | 68 | mock.AddProperties( 69 | MAIN_IFACE, 70 | dbus.Dictionary( 71 | { 72 | "IdleHint": parameters.get("IdleHint", False), 73 | "IdleAction": parameters.get("IdleAction", "ignore"), 74 | "IdleSinceHint": dbus.UInt64(parameters.get("IdleSinceHint", 0)), 75 | "IdleSinceHintMonotonic": dbus.UInt64(parameters.get("IdleSinceHintMonotonic", 0)), 76 | "IdleActionUSec": dbus.UInt64(parameters.get("IdleActionUSec", 1)), 77 | "PreparingForShutdown": parameters.get("PreparingForShutdown", False), 78 | "PreparingForSleep": parameters.get("PreparingForSleep", False), 79 | }, 80 | signature="sv", 81 | ), 82 | ) 83 | 84 | 85 | # 86 | # logind methods which are too big for squeezing into AddMethod() 87 | # 88 | 89 | 90 | @dbus.service.method(MAIN_IFACE, in_signature="", out_signature="a(uso)") 91 | def ListUsers(_): 92 | users = [] 93 | for k, obj in mockobject.objects.items(): 94 | if "/user/" in k: 95 | uid = dbus.UInt32(int(k.split("/")[-1])) 96 | users.append((uid, obj.Get("org.freedesktop.login1.User", "Name"), k)) 97 | return users 98 | 99 | 100 | @dbus.service.method(MAIN_IFACE, in_signature="", out_signature="a(susso)") 101 | def ListSessions(_): 102 | sessions = [] 103 | for k, obj in mockobject.objects.items(): 104 | if "/session/" in k: 105 | session_id = k.split("/")[-1] 106 | uid = obj.Get("org.freedesktop.login1.Session", "User")[0] 107 | username = obj.Get("org.freedesktop.login1.Session", "Name") 108 | seat = obj.Get("org.freedesktop.login1.Session", "Seat")[0] 109 | sessions.append((session_id, uid, username, seat, k)) 110 | return sessions 111 | 112 | 113 | @dbus.service.method(MAIN_IFACE, in_signature="ssss", out_signature="h") 114 | def Inhibit(_, what, who, why, mode): 115 | if not hasattr(mockobject, "inhibitors"): 116 | mockobject.inhibitors = [] 117 | 118 | fd_r, fd_w = os.pipe() 119 | 120 | inhibitor = (what, who, why, mode, 1000, 123456) 121 | mockobject.inhibitors.append(inhibitor) 122 | 123 | def inhibitor_dropped(fd, cond): 124 | # pylint: disable=unused-argument 125 | os.close(fd) 126 | mockobject.inhibitors.remove(inhibitor) 127 | return False 128 | 129 | GLib.unix_fd_add_full(GLib.PRIORITY_HIGH, fd_r, GLib.IO_HUP, inhibitor_dropped) 130 | GLib.idle_add(os.close, fd_w) 131 | 132 | return fd_w 133 | 134 | 135 | @dbus.service.method(MAIN_IFACE, in_signature="", out_signature="a(ssssuu)") 136 | def ListInhibitors(_): 137 | if not hasattr(mockobject, "inhibitors"): 138 | mockobject.inhibitors = [] 139 | 140 | return mockobject.inhibitors 141 | 142 | 143 | # 144 | # Convenience methods on the mock 145 | # 146 | 147 | 148 | @dbus.service.method(MOCK_IFACE, in_signature="s", out_signature="s") 149 | def AddSeat(self, seat): 150 | """Convenience method to add a seat. 151 | 152 | Return the object path of the new seat. 153 | """ 154 | seat_path = "/org/freedesktop/login1/seat/" + seat 155 | if seat_path in mockobject.objects: 156 | raise dbus.exceptions.DBusException(f"Seat {seat} already exists", name=MOCK_IFACE + ".SeatExists") 157 | 158 | self.AddObject( 159 | seat_path, 160 | "org.freedesktop.login1.Seat", 161 | { 162 | "Sessions": dbus.Array([], signature="(so)"), 163 | "CanGraphical": False, 164 | "CanMultiSession": True, 165 | "CanTTY": False, 166 | "IdleHint": False, 167 | "ActiveSession": ("", dbus.ObjectPath("/")), 168 | "Id": seat, 169 | "IdleSinceHint": dbus.UInt64(0), 170 | "IdleSinceHintMonotonic": dbus.UInt64(0), 171 | }, 172 | [("ActivateSession", "s", "", ""), ("Terminate", "", "", "")], 173 | ) 174 | 175 | return seat_path 176 | 177 | 178 | @dbus.service.method(MOCK_IFACE, in_signature="usb", out_signature="s") 179 | def AddUser(self, uid, username, active): 180 | """Convenience method to add a user. 181 | 182 | Return the object path of the new user. 183 | """ 184 | user_path = f"/org/freedesktop/login1/user/{uid}" 185 | if user_path in mockobject.objects: 186 | raise dbus.exceptions.DBusException(f"User {uid} already exists", name=MOCK_IFACE + ".UserExists") 187 | 188 | self.AddObject( 189 | user_path, 190 | "org.freedesktop.login1.User", 191 | { 192 | "DefaultControlGroup": "systemd:/user/" + username, 193 | "Display": ("", dbus.ObjectPath("/")), 194 | "GID": dbus.UInt32(uid), 195 | "IdleHint": False, 196 | "IdleSinceHint": dbus.UInt64(0), 197 | "IdleSinceHintMonotonic": dbus.UInt64(0), 198 | "Linger": False, 199 | "Name": username, 200 | "RuntimePath": f"/run/user/{uid}", 201 | "Service": "", 202 | "Sessions": dbus.Array([], signature="(so)"), 203 | "Slice": f"user-{uid}.slice", 204 | "State": "active" if active else "online", 205 | "Timestamp": dbus.UInt64(42), 206 | "TimestampMonotonic": dbus.UInt64(42), 207 | "UID": dbus.UInt32(uid), 208 | }, 209 | [ 210 | ("Kill", "s", "", ""), 211 | ("Terminate", "", "", ""), 212 | ], 213 | ) 214 | 215 | return user_path 216 | 217 | 218 | @dbus.service.method(MOCK_IFACE, in_signature="ssusb", out_signature="s") 219 | def AddSession(self, session_id, seat, uid, username, active): 220 | """Convenience method to add a session. 221 | 222 | If the given seat and/or user do not exit, they will be created. 223 | 224 | Return the object path of the new session. 225 | """ 226 | seat_path = dbus.ObjectPath(f"/org/freedesktop/login1/seat/{seat}") 227 | if seat_path not in mockobject.objects: 228 | self.AddSeat(seat) 229 | 230 | user_path = dbus.ObjectPath(f"/org/freedesktop/login1/user/{uid}") 231 | if user_path not in mockobject.objects: 232 | self.AddUser(uid, username, active) 233 | 234 | session_path = dbus.ObjectPath(f"/org/freedesktop/login1/session/{session_id}") 235 | if session_path in mockobject.objects: 236 | raise dbus.exceptions.DBusException( 237 | f"Session {session_id} already exists", name=MOCK_IFACE + ".SessionExists" 238 | ) 239 | 240 | self.AddObject( 241 | session_path, 242 | "org.freedesktop.login1.Session", 243 | { 244 | "Controllers": dbus.Array([], signature="s"), 245 | "ResetControllers": dbus.Array([], signature="s"), 246 | "Active": active, 247 | "IdleHint": False, 248 | "LockedHint": False, 249 | "KillProcesses": False, 250 | "Remote": False, 251 | "Class": "user", 252 | "DefaultControlGroup": f"systemd:/user/{username}/{session_id}", 253 | "Display": os.getenv("DISPLAY", ""), 254 | "Id": session_id, 255 | "Name": username, 256 | "RemoteHost": "", 257 | "RemoteUser": "", 258 | "Service": "dbusmock", 259 | "State": "active" if active else "online", 260 | "TTY": "", 261 | "Type": "test", 262 | "Seat": (seat, seat_path), 263 | "User": (dbus.UInt32(uid), user_path), 264 | "Audit": dbus.UInt32(0), 265 | "Leader": dbus.UInt32(1), 266 | "VTNr": dbus.UInt32(1), 267 | "IdleSinceHint": dbus.UInt64(0), 268 | "IdleSinceHintMonotonic": dbus.UInt64(0), 269 | "Timestamp": dbus.UInt64(42), 270 | "TimestampMonotonic": dbus.UInt64(42), 271 | }, 272 | [ 273 | ("Activate", "", "", ""), 274 | ("Kill", "ss", "", ""), 275 | ("Lock", "", "", 'self.EmitSignal("", "Lock", "", [])'), 276 | ("SetIdleHint", "b", "", ""), 277 | ("SetLockedHint", "b", "", 'self.UpdateProperties("", {"LockedHint": args[0]})'), 278 | ("Terminate", "", "", ""), 279 | ("Unlock", "", "", 'self.EmitSignal("", "Unlock", "", [])'), 280 | ], 281 | ) 282 | 283 | # add session to seat 284 | obj_seat = mockobject.objects[seat_path] 285 | cur_sessions = obj_seat.Get("org.freedesktop.login1.Seat", "Sessions") 286 | cur_sessions.append((session_id, session_path)) 287 | obj_seat.Set("org.freedesktop.login1.Seat", "Sessions", cur_sessions) 288 | obj_seat.Set("org.freedesktop.login1.Seat", "ActiveSession", (session_id, session_path)) 289 | 290 | # add session to user 291 | obj_user = mockobject.objects[user_path] 292 | cur_sessions = obj_user.Get("org.freedesktop.login1.User", "Sessions") 293 | cur_sessions.append((session_id, session_path)) 294 | obj_user.Set("org.freedesktop.login1.User", "Sessions", cur_sessions) 295 | 296 | return session_path 297 | -------------------------------------------------------------------------------- /dbusmock/templates/low_memory_monitor.py: -------------------------------------------------------------------------------- 1 | """low-memory-monitor mock template 2 | 3 | This creates the expected methods and properties of the main 4 | org.freedesktop.LowMemoryMonitor object. 5 | 6 | This provides only the 2.0 D-Bus API of low-memory-monitor. 7 | """ 8 | 9 | # This program is free software; you can redistribute it and/or modify it under 10 | # the terms of the GNU Lesser General Public License as published by the Free 11 | # Software Foundation; either version 3 of the License, or (at your option) any 12 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 13 | # of the license. 14 | 15 | __author__ = "Bastien Nocera" 16 | __copyright__ = """ 17 | (c) 2019, Red Hat Inc. 18 | (c) 2017 - 2022 Martin Pitt 19 | """ 20 | 21 | import dbus 22 | 23 | from dbusmock import MOCK_IFACE 24 | 25 | BUS_NAME = "org.freedesktop.LowMemoryMonitor" 26 | MAIN_OBJ = "/org/freedesktop/LowMemoryMonitor" 27 | MAIN_IFACE = "org.freedesktop.LowMemoryMonitor" 28 | SYSTEM_BUS = True 29 | 30 | 31 | def load(mock, _parameters): 32 | # Loaded! 33 | mock.loaded = True 34 | 35 | 36 | @dbus.service.method(MOCK_IFACE, in_signature="y", out_signature="") 37 | def EmitWarning(self, state): 38 | self.EmitSignal(MAIN_IFACE, "LowMemoryWarning", "y", [dbus.Byte(state)]) 39 | -------------------------------------------------------------------------------- /dbusmock/templates/modemmanager.py: -------------------------------------------------------------------------------- 1 | """ModemManager mock template 2 | 3 | This creates the expected methods and properties of the main 4 | ModemManager object, but no devices. You can specify any property 5 | such as DaemonVersion in "parameters". 6 | """ 7 | 8 | # This program is free software; you can redistribute it and/or modify it under 9 | # the terms of the GNU Lesser General Public License as published by the Free 10 | # Software Foundation; either version 3 of the License, or (at your option) any 11 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 12 | # of the license. 13 | 14 | __author__ = "Guido Günther" 15 | __copyright__ = "2024 The Phosh Developers" 16 | 17 | import dbus 18 | 19 | from dbusmock import MOCK_IFACE, OBJECT_MANAGER_IFACE, mockobject 20 | 21 | BUS_NAME = "org.freedesktop.ModemManager1" 22 | MAIN_OBJ = "/org/freedesktop/ModemManager1" 23 | MAIN_IFACE = "org.freedesktop.ModemManager1" 24 | SYSTEM_BUS = True 25 | IS_OBJECT_MANAGER = False 26 | MODEM_IFACE = "org.freedesktop.ModemManager1.Modem" 27 | MODEM_3GPP_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp" 28 | MODEM_CELL_BROADCAST_IFACE = "org.freedesktop.ModemManager1.Modem.CellBroadcast" 29 | MODEM_VOICE_IFACE = "org.freedesktop.ModemManager1.Modem.Voice" 30 | SIM_IFACE = "org.freedesktop.ModemManager1.Sim" 31 | CBM_IFACE = "org.freedesktop.ModemManager1.Cbm" 32 | SIMPLE_MODEM_PATH = "/org/freedesktop/ModemManager1/Modems/8" 33 | 34 | 35 | class MMModemMode: 36 | """ 37 | See 38 | https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemMode 39 | """ 40 | 41 | MODE_NONE = 0 42 | MODE_CS = 1 << 0 43 | MODE_2G = 1 << 1 44 | MODE_3G = 1 << 2 45 | MODE_4G = 1 << 3 46 | MODE_5G = 1 << 4 47 | 48 | 49 | class MMModemState: 50 | """ 51 | See 52 | https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemState 53 | """ 54 | 55 | STATE_FAILED = -1 56 | STATE_UNKNOWN = 0 57 | STATE_INITIALIZING = 1 58 | STATE_LOCKED = 2 59 | STATE_DISABLED = 3 60 | STATE_DISABLING = 4 61 | STATE_ENABLING = 5 62 | STATE_ENABLED = 6 63 | STATE_SEARCHING = 7 64 | STATE_REGISTERED = 8 65 | STATE_DISCONNECTING = 9 66 | STATE_CONNECTING = 10 67 | STATE_CONNECTED = 11 68 | 69 | 70 | class MMModemPowerState: 71 | """ 72 | See 73 | https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemPowerState 74 | """ 75 | 76 | POWER_STATE_UNKNOWN = 0 77 | POWER_STATE_OFF = 1 78 | POWER_STATE_LOW = 2 79 | POWER_STATE_ON = 3 80 | 81 | 82 | class MMModemAccesssTechnology: 83 | """ 84 | See 85 | https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemAccessTechnology 86 | """ 87 | 88 | ACCESS_TECHNOLOGY_UNKNOWN = 0 89 | ACCESS_TECHNOLOGY_POTS = 1 << 0 90 | ACCESS_TECHNOLOGY_GSM = 1 << 1 91 | ACCESS_TECHNOLOGY_GSM_COMPACT = 1 << 2 92 | ACCESS_TECHNOLOGY_GPRS = 1 << 3 93 | ACCESS_TECHNOLOGY_EDGE = 1 << 4 94 | ACCESS_TECHNOLOGY_UMTS = 1 << 5 95 | ACCESS_TECHNOLOGY_HSDPA = 1 << 6 96 | ACCESS_TECHNOLOGY_HSUPA = 1 << 7 97 | ACCESS_TECHNOLOGY_HSPA = 1 << 8 98 | ACCESS_TECHNOLOGY_HSPA_PLUS = 1 << 9 99 | ACCESS_TECHNOLOGY_1XRTT = 1 << 10 100 | ACCESS_TECHNOLOGY_EVDO0 = 1 << 11 101 | ACCESS_TECHNOLOGY_EVDOA = 1 << 12 102 | ACCESS_TECHNOLOGY_EVDOB = 1 << 13 103 | ACCESS_TECHNOLOGY_LTE = 1 << 14 104 | ACCESS_TECHNOLOGY_5GNR = 1 << 15 105 | ACCESS_TECHNOLOGY_LTE_CAT_M = 1 << 16 106 | ACCESS_TECHNOLOGY_LTE_NB_IOT = 1 << 17 107 | 108 | 109 | def load(mock, parameters): 110 | methods = [ 111 | ("ScanDevices", "", "", ""), 112 | ] 113 | 114 | props = dbus.Dictionary( 115 | { 116 | "Version": parameters.get("DaemonVersion", "1.22"), 117 | }, 118 | signature="sv", 119 | ) 120 | 121 | mock.AddMethods(MAIN_IFACE, methods) 122 | mock.AddProperties(MAIN_IFACE, props) 123 | 124 | cond = "k != '/'" if mock.path == "/" else f"k.startswith('{mock.path}/Modems/')" 125 | code = f"ret = {{dbus.ObjectPath(k): objects[k].props for k in objects.keys() if {cond} }}" 126 | mock.AddMethod(OBJECT_MANAGER_IFACE, "GetManagedObjects", "", "a{oa{sa{sv}}}", code) 127 | 128 | 129 | def listCbm(_): 130 | paths = [] 131 | for path in mockobject.objects: 132 | if path.startswith("/org/freedesktop/ModemManager1/Cbm/"): 133 | paths.append(dbus.ObjectPath(path)) 134 | return paths 135 | 136 | 137 | def deleteCbm(self, cbm_path): 138 | obj = mockobject.objects.get(cbm_path) 139 | if obj is None: 140 | return 141 | 142 | modem_obj = mockobject.objects[SIMPLE_MODEM_PATH] 143 | self.RemoveObject(cbm_path) 144 | modem_obj.EmitSignal( 145 | MODEM_CELL_BROADCAST_IFACE, 146 | "Deleted", 147 | "o", 148 | [ 149 | cbm_path, 150 | ], 151 | ) 152 | 153 | 154 | def setChannels(_self, channels): 155 | modem_obj = mockobject.objects[SIMPLE_MODEM_PATH] 156 | 157 | modem_obj.UpdateProperties( 158 | MODEM_CELL_BROADCAST_IFACE, 159 | { 160 | "Channels": dbus.Array(channels), 161 | }, 162 | ) 163 | 164 | 165 | @dbus.service.method(MOCK_IFACE, in_signature="", out_signature="ss") 166 | def AddSimpleModem(self): 167 | """Convenience method to add a simple Modem object 168 | 169 | Please note that this does not set any global properties. 170 | 171 | Returns the new object path. 172 | """ 173 | modem_path = SIMPLE_MODEM_PATH 174 | sim_path = "/org/freedesktop/ModemManager1/SIM/2" 175 | manager = mockobject.objects[MAIN_OBJ] 176 | 177 | modem_props = { 178 | "State": dbus.Int32(MMModemState.STATE_ENABLED), 179 | "Model": dbus.String("E1750"), 180 | "Revision": dbus.String("11.126.08.01.00"), 181 | "AccessTechnologies": dbus.UInt32(MMModemAccesssTechnology.ACCESS_TECHNOLOGY_LTE), 182 | "PowerState": dbus.UInt32(MMModemPowerState.POWER_STATE_ON), 183 | "UnlockRequired": dbus.UInt32(0), 184 | "UnlockRetries": dbus.Dictionary([], signature="uu"), 185 | "CurrentModes": dbus.Struct( 186 | (dbus.UInt32(MMModemMode.MODE_4G), dbus.UInt32(MMModemMode.MODE_4G)), signature="(uu)" 187 | ), 188 | "SignalQuality": dbus.Struct( 189 | (dbus.UInt32(70), dbus.Boolean(True)), 190 | ), 191 | "Sim": dbus.ObjectPath(sim_path), 192 | "SupportedModes": [ 193 | (dbus.UInt32(MMModemMode.MODE_4G), dbus.UInt32(MMModemMode.MODE_4G)), 194 | (dbus.UInt32(MMModemMode.MODE_3G | MMModemMode.MODE_2G), dbus.UInt32(MMModemMode.MODE_3G)), 195 | ], 196 | "SupportedBands": [dbus.UInt32(0)], 197 | } 198 | self.AddObject(modem_path, MODEM_IFACE, modem_props, []) 199 | 200 | modem_3gpp_props = { 201 | "Imei": dbus.String("doesnotmatter"), 202 | "OperatorName": dbus.String("TheOperator"), 203 | "OperatorCode": dbus.String("00101"), 204 | "Pco": dbus.Array([], signature="(ubay)"), 205 | } 206 | modem = mockobject.objects[modem_path] 207 | modem.AddProperties(MODEM_3GPP_IFACE, modem_3gpp_props) 208 | 209 | modem_cell_broadcast_props = { 210 | "Channels": dbus.Array([], signature="(uu)"), 211 | "CellBroadcasts": dbus.Array([], signature="o"), 212 | } 213 | modem_cell_broadcast_methods = [ 214 | ("List", "", "ao", listCbm), 215 | ("Delete", "o", "", deleteCbm), 216 | ("SetChannels", "a(uu)", "", setChannels), 217 | ] 218 | modem.AddProperties(MODEM_CELL_BROADCAST_IFACE, modem_cell_broadcast_props) 219 | modem.AddMethods(MODEM_CELL_BROADCAST_IFACE, modem_cell_broadcast_methods) 220 | 221 | modem_voice_props = { 222 | "Calls": dbus.Array([], signature="o"), 223 | "EmergencyOnly": False, 224 | } 225 | 226 | modem.call_waiting = False 227 | modem_voice_methods = [ 228 | ("CallWaitingQuery", "", "b", "ret = self.call_waiting"), 229 | ("CallWaitingSetup", "b", "", "self.call_waiting = args[0]"), 230 | ] 231 | modem.AddProperties(MODEM_VOICE_IFACE, modem_voice_props) 232 | modem.AddMethods(MODEM_VOICE_IFACE, modem_voice_methods) 233 | 234 | manager.EmitSignal( 235 | OBJECT_MANAGER_IFACE, 236 | "InterfacesAdded", 237 | "oa{sa{sv}}", 238 | [ 239 | dbus.ObjectPath(modem_path), 240 | { 241 | MODEM_IFACE: modem_props, 242 | MODEM_3GPP_IFACE: modem_3gpp_props, 243 | MODEM_CELL_BROADCAST_IFACE: modem_cell_broadcast_props, 244 | MODEM_VOICE_IFACE: modem_voice_props, 245 | }, 246 | ], 247 | ) 248 | 249 | sim_props = { 250 | "Active": dbus.Boolean(True), 251 | "Imsi": dbus.String("doesnotmatter"), 252 | "PreferredNetworks": dbus.Array([], signature="(su)"), 253 | } 254 | self.AddObject(sim_path, SIM_IFACE, sim_props, []) 255 | 256 | return (modem_path, sim_path) 257 | 258 | 259 | @dbus.service.method(MOCK_IFACE, in_signature="uus", out_signature="s") 260 | def AddCbm(self, state, channel, text): 261 | """Convenience method to add a cell broadcast message 262 | 263 | Returns the new object path. 264 | """ 265 | n = 1 266 | while mockobject.objects.get(f"/org/freedesktop/ModemManager1/Cbm/{n}") is not None: 267 | n += 1 268 | cbm_path = f"/org/freedesktop/ModemManager1/Cbm/{n}" 269 | 270 | cbm_props = { 271 | "State": dbus.UInt32(state), 272 | "Channel": dbus.UInt32(channel), 273 | "Text": dbus.String(text), 274 | "MessageCode": dbus.UInt32(0), 275 | "Update": dbus.UInt32(0), 276 | } 277 | self.AddObject(cbm_path, CBM_IFACE, cbm_props, []) 278 | 279 | modem_obj = mockobject.objects[SIMPLE_MODEM_PATH] 280 | paths = listCbm(self) 281 | modem_obj.UpdateProperties( 282 | MODEM_CELL_BROADCAST_IFACE, 283 | { 284 | "CellBroadcasts": dbus.Array(paths), 285 | }, 286 | ) 287 | 288 | modem_obj.EmitSignal( 289 | MODEM_CELL_BROADCAST_IFACE, 290 | "Added", 291 | "o", 292 | [ 293 | dbus.ObjectPath(cbm_path), 294 | ], 295 | ) 296 | 297 | return cbm_path 298 | -------------------------------------------------------------------------------- /dbusmock/templates/notification_daemon.py: -------------------------------------------------------------------------------- 1 | """notification-daemon mock template 2 | 3 | This creates the expected methods and properties of the notification-daemon 4 | services, but no devices. You can specify non-default capabilities in 5 | "parameters". 6 | """ 7 | 8 | # This program is free software; you can redistribute it and/or modify it under 9 | # the terms of the GNU Lesser General Public License as published by the Free 10 | # Software Foundation; either version 3 of the License, or (at your option) any 11 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 12 | # of the license. 13 | 14 | __author__ = "Martin Pitt" 15 | __copyright__ = """ 16 | (c) 2012 Canonical Ltd. 17 | (c) 2017 - 2022 Martin Pitt 18 | """ 19 | 20 | BUS_NAME = "org.freedesktop.Notifications" 21 | MAIN_OBJ = "/org/freedesktop/Notifications" 22 | MAIN_IFACE = "org.freedesktop.Notifications" 23 | SYSTEM_BUS = False 24 | 25 | # default capabilities, can be modified with "capabilities" parameter 26 | default_caps = [ 27 | "body", 28 | "body-markup", 29 | "icon-static", 30 | "image/svg+xml", 31 | "private-synchronous", 32 | "append", 33 | "private-icon-only", 34 | "truncation", 35 | ] 36 | 37 | 38 | def load(mock, parameters): 39 | caps = parameters["capabilities"].split() if "capabilities" in parameters else default_caps 40 | 41 | # next notification ID 42 | mock.next_id = 1 43 | 44 | mock.AddMethods( 45 | MAIN_IFACE, 46 | [ 47 | ("GetCapabilities", "", "as", f"ret = {caps!r}"), 48 | ( 49 | "CloseNotification", 50 | "i", 51 | "", 52 | "if args[0] < self.next_id: self.EmitSignal('', 'NotificationClosed', 'uu', [args[0], 1])", 53 | ), 54 | ("GetServerInformation", "", "ssss", 'ret = ("mock-notify", "test vendor", "1.0", "1.1")'), 55 | ( 56 | "Notify", 57 | "susssasa{sv}i", 58 | "u", 59 | """if args[1]: 60 | ret = args[1] 61 | else: 62 | ret = self.next_id 63 | self.next_id += 1 64 | """, 65 | ), 66 | ], 67 | ) 68 | -------------------------------------------------------------------------------- /dbusmock/templates/polkitd.py: -------------------------------------------------------------------------------- 1 | """polkitd mock template 2 | 3 | This creates the expected methods and properties of the main 4 | org.freedesktop.PolicyKit1 object. By default, all actions are rejected. You 5 | can call AllowUnknown() and SetAllowed() on the mock D-Bus interface to control 6 | which actions are allowed. 7 | """ 8 | 9 | # This program is free software; you can redistribute it and/or modify it under 10 | # the terms of the GNU Lesser General Public License as published by the Free 11 | # Software Foundation; either version 3 of the License, or (at your option) any 12 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 13 | # of the license. 14 | 15 | __author__ = "Martin Pitt" 16 | __copyright__ = """ 17 | (c) 2013-2021 Canonical Ltd. 18 | (c) 2017 - 2022 Martin Pitt 19 | """ 20 | 21 | import time 22 | 23 | import dbus 24 | 25 | from dbusmock import MOCK_IFACE 26 | 27 | BUS_NAME = "org.freedesktop.PolicyKit1" 28 | MAIN_OBJ = "/org/freedesktop/PolicyKit1/Authority" 29 | MAIN_IFACE = "org.freedesktop.PolicyKit1.Authority" 30 | SYSTEM_BUS = True 31 | 32 | 33 | def load(mock, _parameters): 34 | mock.AddProperties( 35 | MAIN_IFACE, 36 | dbus.Dictionary( 37 | { 38 | "BackendName": "local", 39 | "BackendVersion": "0.8.15", 40 | "BackendFeatures": dbus.UInt32(1), 41 | }, 42 | signature="sv", 43 | ), 44 | ) 45 | 46 | # default state 47 | mock.allow_unknown = False 48 | mock.allowed = [] 49 | mock.delay = 0 50 | mock.simulate_hang = False 51 | mock.hanging_actions = [] 52 | mock.hanging_calls = [] 53 | 54 | 55 | @dbus.service.method( 56 | MAIN_IFACE, in_signature="(sa{sv})sa{ss}us", out_signature="(bba{ss})", async_callbacks=("ok_cb", "_err_cb") 57 | ) 58 | def CheckAuthorization(self, _subject, action_id, _details, _flags, _cancellation_id, ok_cb, _err_cb): 59 | time.sleep(self.delay) 60 | allowed = action_id in self.allowed or self.allow_unknown 61 | ret = (allowed, False, {"test": "test"}) 62 | 63 | if self.simulate_hang or action_id in self.hanging_actions: 64 | self.hanging_calls.append((ok_cb, ret)) 65 | else: 66 | ok_cb(ret) 67 | 68 | 69 | @dbus.service.method(MAIN_IFACE, in_signature="(sa{sv})ss") 70 | def RegisterAuthenticationAgent(_self, _subject, _locale, _object_path): 71 | pass 72 | 73 | 74 | @dbus.service.method(MOCK_IFACE, in_signature="b", out_signature="") 75 | def AllowUnknown(self, default): 76 | """Control whether unknown actions are allowed 77 | 78 | This controls the return value of CheckAuthorization for actions which were 79 | not explicitly allowed by SetAllowed(). 80 | """ 81 | self.allow_unknown = default 82 | 83 | 84 | @dbus.service.method(MOCK_IFACE, in_signature="as", out_signature="") 85 | def SetAllowed(self, actions): 86 | """Set allowed actions""" 87 | 88 | self.allowed = actions 89 | 90 | 91 | @dbus.service.method(MOCK_IFACE, in_signature="d", out_signature="") 92 | def SetDelay(self, delay): 93 | """Makes the CheckAuthorization() method to delay""" 94 | self.delay = delay 95 | 96 | 97 | @dbus.service.method(MOCK_IFACE, in_signature="b", out_signature="") 98 | def SimulateHang(self, hang): 99 | """Makes the CheckAuthorization() method to hang""" 100 | self.simulate_hang = hang 101 | 102 | 103 | @dbus.service.method(MOCK_IFACE, in_signature="as", out_signature="") 104 | def SimulateHangActions(self, actions): 105 | """Makes the CheckAuthorization() method to hang on such actions""" 106 | self.hanging_actions = actions 107 | 108 | 109 | @dbus.service.method(MOCK_IFACE, in_signature="", out_signature="") 110 | def ReleaseHangingCalls(self): 111 | """Calls all the hanging callbacks""" 112 | for cb, ret in self.hanging_calls: 113 | cb(ret) 114 | self.hanging_calls = [] 115 | 116 | 117 | @dbus.service.method(MOCK_IFACE, in_signature="", out_signature="b") 118 | def HaveHangingCalls(self): 119 | """Check if we've hangling calls""" 120 | return len(self.hanging_calls) 121 | -------------------------------------------------------------------------------- /dbusmock/templates/power_profiles_daemon.py: -------------------------------------------------------------------------------- 1 | """power-profiles-daemon < 0.20 mock template 2 | 3 | This creates the expected methods and properties of the main 4 | net.hadess.PowerProfiles object. 5 | 6 | This provides only the non-deprecated D-Bus API as of version 0.9. 7 | Note that this template is deprecated: Version 0.20 listens on a different 8 | bus name/object path, it is provided in upower_power_profiles_daemon.py 9 | """ 10 | 11 | # This program is free software; you can redistribute it and/or modify it under 12 | # the terms of the GNU Lesser General Public License as published by the Free 13 | # Software Foundation; either version 3 of the License, or (at your option) any 14 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 15 | # of the license. 16 | 17 | __author__ = "Bastien Nocera" 18 | __copyright__ = """ 19 | (c) 2021, Red Hat Inc. 20 | (c) 2017 - 2022 Martin Pitt 21 | """ 22 | 23 | import dbus 24 | 25 | BUS_NAME = "net.hadess.PowerProfiles" 26 | MAIN_OBJ = "/net/hadess/PowerProfiles" 27 | MAIN_IFACE = "net.hadess.PowerProfiles" 28 | SYSTEM_BUS = True 29 | 30 | 31 | def hold_profile(self, profile, reason, application_id): 32 | self.cookie += 1 33 | element = {"Profile": profile, "Reason": reason, "ApplicationId": application_id} 34 | self.holds[self.cookie] = element 35 | self.props[MAIN_IFACE]["ActiveProfileHolds"] = [] 36 | for value in self.holds.values(): 37 | self.props[MAIN_IFACE]["ActiveProfileHolds"].append(value) 38 | return self.cookie 39 | 40 | 41 | def release_profile(self, cookie): 42 | self.holds.pop(cookie) 43 | self.props[MAIN_IFACE]["ActiveProfileHolds"] = [] 44 | for value in self.holds.values(): 45 | self.props[MAIN_IFACE]["ActiveProfileHolds"].append(value) 46 | if len(self.props[MAIN_IFACE]["ActiveProfileHolds"]) == 0: 47 | self.props[MAIN_IFACE]["ActiveProfileHolds"] = dbus.Array([], signature="(aa{sv})") 48 | 49 | 50 | def load(mock, parameters): 51 | # Loaded! 52 | mock.loaded = True 53 | mock.cookie = 0 54 | mock.hold_profile = hold_profile 55 | mock.release_profile = release_profile 56 | mock.holds = {} 57 | 58 | props = { 59 | "ActiveProfile": parameters.get("ActiveProfile", "balanced"), 60 | "PerformanceDegraded": parameters.get("PerformanceDegraded", ""), 61 | "Profiles": [ 62 | dbus.Dictionary({"Profile": "power-saver", "Driver": "dbusmock"}, signature="sv"), 63 | dbus.Dictionary({"Profile": "balanced", "Driver": "dbusmock"}, signature="sv"), 64 | dbus.Dictionary({"Profile": "performance", "Driver": "dbusmock"}, signature="sv"), 65 | ], 66 | "Actions": dbus.Array([], signature="s"), 67 | "ActiveProfileHolds": dbus.Array([], signature="(aa{sv})"), 68 | } 69 | mock.AddProperties(MAIN_IFACE, dbus.Dictionary(props, signature="sv")) 70 | 71 | mock.AddMethods( 72 | MAIN_IFACE, 73 | [ 74 | ("HoldProfile", "sss", "u", "ret = self.hold_profile(self, args[0], args[1], args[2])"), 75 | ("ReleaseProfile", "u", "", "self.release_profile(self, args[0])"), 76 | ], 77 | ) 78 | -------------------------------------------------------------------------------- /dbusmock/templates/systemd.py: -------------------------------------------------------------------------------- 1 | """systemd mock template 2 | """ 3 | 4 | # This program is free software; you can redistribute it and/or modify it under 5 | # the terms of the GNU Lesser General Public License as published by the Free 6 | # Software Foundation; either version 3 of the License, or (at your option) any 7 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 8 | # of the license. 9 | 10 | __author__ = "Jonas Ådahl" 11 | __copyright__ = """ 12 | (c) 2021 Red Hat 13 | (c) 2017 - 2022 Martin Pitt 14 | """ 15 | 16 | import dbus 17 | from gi.repository import GLib 18 | 19 | from dbusmock import MOCK_IFACE, mockobject 20 | 21 | BUS_PREFIX = "org.freedesktop.systemd1" 22 | PATH_PREFIX = "/org/freedesktop/systemd1" 23 | 24 | 25 | BUS_NAME = BUS_PREFIX 26 | MAIN_OBJ = PATH_PREFIX 27 | MAIN_IFACE = BUS_PREFIX + ".Manager" 28 | UNIT_IFACE = BUS_PREFIX + ".Unit" 29 | SYSTEM_BUS = True 30 | 31 | 32 | def load(mock, _parameters): 33 | mock.next_job_id = 1 34 | mock.units = {} 35 | 36 | mock.AddProperties(MAIN_IFACE, {"Version": "v246"}) 37 | 38 | 39 | def escape_unit_name(name): 40 | for s in [".", "-"]: 41 | name = name.replace(s, "_") 42 | return name 43 | 44 | 45 | def emit_job_new_remove(mock, job_id, job_path, name): 46 | mock.EmitSignal(MAIN_IFACE, "JobNew", "uos", [job_id, job_path, name]) 47 | mock.EmitSignal(MAIN_IFACE, "JobRemoved", "uoss", [job_id, job_path, name, "done"]) 48 | 49 | 50 | @dbus.service.method(MAIN_IFACE, in_signature="ss", out_signature="o") 51 | def StartUnit(self, name, _mode): 52 | job_id = self.next_job_id 53 | self.next_job_id += 1 54 | 55 | job_path = f"{PATH_PREFIX}/Job/{job_id}" 56 | GLib.idle_add(lambda: emit_job_new_remove(self, job_id, job_path, name)) 57 | 58 | unit_path = self.units[str(name)] 59 | unit = mockobject.objects[unit_path] 60 | unit.UpdateProperties(UNIT_IFACE, {"ActiveState": "active"}) 61 | 62 | return job_path 63 | 64 | 65 | @dbus.service.method(MAIN_IFACE, in_signature="ssa(sv)a(sa(sv))", out_signature="o") 66 | def StartTransientUnit(self, name, _mode, _properties, _aux): 67 | job_id = self.next_job_id 68 | self.next_job_id += 1 69 | 70 | job_path = f"{PATH_PREFIX}/Job/{job_id}" 71 | GLib.idle_add(lambda: emit_job_new_remove(self, job_id, job_path, name)) 72 | 73 | return job_path 74 | 75 | 76 | @dbus.service.method(MAIN_IFACE, in_signature="ss", out_signature="o") 77 | def StopUnit(self, name, _mode): 78 | job_id = self.next_job_id 79 | self.next_job_id += 1 80 | 81 | job_path = f"{PATH_PREFIX}/Job/{job_id}" 82 | GLib.idle_add(lambda: emit_job_new_remove(self, job_id, job_path, name)) 83 | 84 | unit_path = self.units[str(name)] 85 | unit = mockobject.objects[unit_path] 86 | unit.UpdateProperties(UNIT_IFACE, {"ActiveState": "inactive"}) 87 | return job_path 88 | 89 | 90 | @dbus.service.method(MAIN_IFACE, in_signature="s", out_signature="o") 91 | def GetUnit(self, name): 92 | return self.units[str(name)] 93 | 94 | 95 | @dbus.service.method(MOCK_IFACE, in_signature="s") 96 | def AddMockUnit(self, name): 97 | unit_path = f"{PATH_PREFIX}/unit/{escape_unit_name(name)}" 98 | self.units[str(name)] = unit_path 99 | self.AddObject( 100 | unit_path, 101 | UNIT_IFACE, 102 | { 103 | "Id": name, 104 | "Names": [name], 105 | "LoadState": "loaded", 106 | "ActiveState": "inactive", 107 | }, 108 | [], 109 | ) 110 | -------------------------------------------------------------------------------- /dbusmock/templates/timedated.py: -------------------------------------------------------------------------------- 1 | """systemd timedated mock template 2 | 3 | This creates the expected methods and properties of the main 4 | org.freedesktop.timedate object. You can specify D-Bus property values like 5 | "Timezone" or "NTP" in "parameters". 6 | """ 7 | 8 | # This program is free software; you can redistribute it and/or modify it under 9 | # the terms of the GNU Lesser General Public License as published by the Free 10 | # Software Foundation; either version 3 of the License, or (at your option) any 11 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 12 | # of the license. 13 | 14 | __author__ = "Iain Lane" 15 | __copyright__ = """ 16 | (c) 2013 Canonical Ltd. 17 | (c) 2017 - 2022 Martin Pitt 18 | """ 19 | 20 | import dbus 21 | 22 | BUS_NAME = "org.freedesktop.timedate1" 23 | MAIN_OBJ = "/org/freedesktop/timedate1" 24 | MAIN_IFACE = "org.freedesktop.timedate1" 25 | SYSTEM_BUS = True 26 | 27 | 28 | def setProperty(prop): 29 | return f'self.Set("{MAIN_IFACE}", "{prop}", args[0])' 30 | 31 | 32 | def load(mock, parameters): 33 | mock.AddMethods( 34 | MAIN_IFACE, 35 | [ 36 | # There's nothing this can usefully do, but provide it for compatibility 37 | ("SetTime", "xbb", "", ""), 38 | ("SetTimezone", "sb", "", setProperty("Timezone")), 39 | ("SetLocalRTC", "bbb", "", setProperty("LocalRTC")), 40 | ("SetNTP", "bb", "", setProperty("NTP") + "; " + setProperty("NTPSynchronized")), 41 | ], 42 | ) 43 | 44 | mock.AddProperties( 45 | MAIN_IFACE, 46 | dbus.Dictionary( 47 | { 48 | "Timezone": parameters.get("Timezone", "Etc/Utc"), 49 | "LocalRTC": parameters.get("LocalRTC", False), 50 | "NTP": parameters.get("NTP", True), 51 | "NTPSynchronized": parameters.get("NTP", True), 52 | "CanNTP": parameters.get("CanNTP", True), 53 | }, 54 | signature="sv", 55 | ), 56 | ) 57 | -------------------------------------------------------------------------------- /dbusmock/templates/upower.py: -------------------------------------------------------------------------------- 1 | """upowerd mock template 2 | 3 | This creates the expected methods and properties of the main 4 | org.freedesktop.UPower object, but no devices. You can specify any property 5 | such as 'OnLowBattery' or the return value of 'SuspendAllowed', 6 | 'HibernateAllowed', and 'GetCriticalAction' in "parameters". 7 | 8 | This provides the 1.0 D-Bus API of upower. 9 | """ 10 | 11 | # This program is free software; you can redistribute it and/or modify it under 12 | # the terms of the GNU Lesser General Public License as published by the Free 13 | # Software Foundation; either version 3 of the License, or (at your option) any 14 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 15 | # of the license. 16 | 17 | __author__ = "Martin Pitt" 18 | __copyright__ = """ 19 | (c) 2012, 2013 Canonical Ltd. 20 | (c) 2017 - 2022 Martin Pitt 21 | """ 22 | 23 | import dbus 24 | 25 | import dbusmock 26 | from dbusmock import MOCK_IFACE, mockobject 27 | 28 | BUS_NAME = "org.freedesktop.UPower" 29 | MAIN_OBJ = "/org/freedesktop/UPower" 30 | MAIN_IFACE = "org.freedesktop.UPower" 31 | SYSTEM_BUS = True 32 | DEVICE_IFACE = "org.freedesktop.UPower.Device" 33 | 34 | 35 | def load(mock, parameters): 36 | mock.AddMethods( 37 | MAIN_IFACE, 38 | [ 39 | ( 40 | "EnumerateDevices", 41 | "", 42 | "ao", 43 | 'ret = [k for k in objects.keys() if "/devices" in k and not k.endswith("/DisplayDevice")]', 44 | ), 45 | ], 46 | ) 47 | 48 | props = dbus.Dictionary( 49 | { 50 | "DaemonVersion": parameters.get("DaemonVersion", "0.99"), 51 | "OnBattery": parameters.get("OnBattery", False), 52 | "LidIsPresent": parameters.get("LidIsPresent", True), 53 | "LidIsClosed": parameters.get("LidIsClosed", False), 54 | "LidForceSleep": parameters.get("LidForceSleep", True), 55 | "IsDocked": parameters.get("IsDocked", False), 56 | }, 57 | signature="sv", 58 | ) 59 | 60 | mock.AddMethods( 61 | MAIN_IFACE, 62 | [ 63 | ("GetCriticalAction", "", "s", f'ret = "{parameters.get("GetCriticalAction", "HybridSleep")}"'), 64 | ("GetDisplayDevice", "", "o", 'ret = "/org/freedesktop/UPower/devices/DisplayDevice"'), 65 | ], 66 | ) 67 | 68 | mock.p_display_dev = "/org/freedesktop/UPower/devices/DisplayDevice" 69 | 70 | # add Display device; for defined properties, see 71 | # http://cgit.freedesktop.org/upower/tree/src/org.freedesktop.UPower.xml 72 | mock.AddObject( 73 | mock.p_display_dev, 74 | DEVICE_IFACE, 75 | { 76 | "Type": dbus.UInt32(0), 77 | "State": dbus.UInt32(0), 78 | "Percentage": dbus.Double(0.0), 79 | "Energy": dbus.Double(0.0), 80 | "EnergyFull": dbus.Double(0.0), 81 | "EnergyRate": dbus.Double(0.0), 82 | "TimeToEmpty": dbus.Int64(0), 83 | "TimeToFull": dbus.Int64(0), 84 | "IsPresent": dbus.Boolean(False), 85 | "IconName": dbus.String(""), 86 | # LEVEL_NONE 87 | "WarningLevel": dbus.UInt32(1), 88 | }, 89 | [ 90 | ("Refresh", "", "", ""), 91 | ], 92 | ) 93 | 94 | mock.AddProperties(MAIN_IFACE, props) 95 | 96 | 97 | @dbus.service.method(MOCK_IFACE, in_signature="ss", out_signature="s") 98 | def AddAC(self, device_name, model_name): 99 | """Convenience method to add an AC object 100 | 101 | You have to specify a device name which must be a valid part of an object 102 | path, e. g. "mock_ac", and an arbitrary model name. 103 | 104 | Please note that this does not set any global properties such as 105 | "on-battery". 106 | 107 | Returns the new object path. 108 | """ 109 | path = "/org/freedesktop/UPower/devices/" + device_name 110 | self.AddObject( 111 | path, 112 | DEVICE_IFACE, 113 | { 114 | "PowerSupply": dbus.Boolean(True), 115 | "Model": dbus.String(model_name), 116 | "Online": dbus.Boolean(True), 117 | }, 118 | [], 119 | ) 120 | self.EmitSignal(MAIN_IFACE, "DeviceAdded", "o", [path]) 121 | return path 122 | 123 | 124 | @dbus.service.method(MOCK_IFACE, in_signature="ssdx", out_signature="s") 125 | def AddDischargingBattery(self, device_name, model_name, percentage, seconds_to_empty): 126 | """Convenience method to add a discharging battery object 127 | 128 | You have to specify a device name which must be a valid part of an object 129 | path, e. g. "mock_ac", an arbitrary model name, the charge percentage, and 130 | the seconds until the battery is empty. 131 | 132 | Please note that this does not set any global properties such as 133 | "on-battery". 134 | 135 | Returns the new object path. 136 | """ 137 | path = "/org/freedesktop/UPower/devices/" + device_name 138 | self.AddObject( 139 | path, 140 | DEVICE_IFACE, 141 | { 142 | "PowerSupply": dbus.Boolean(True), 143 | "IsPresent": dbus.Boolean(True), 144 | "Model": dbus.String(model_name), 145 | "Percentage": dbus.Double(percentage), 146 | "TimeToEmpty": dbus.Int64(seconds_to_empty), 147 | "EnergyFull": dbus.Double(100.0), 148 | "Energy": dbus.Double(percentage), 149 | # UP_DEVICE_STATE_DISCHARGING 150 | "State": dbus.UInt32(2), 151 | # UP_DEVICE_KIND_BATTERY 152 | "Type": dbus.UInt32(2), 153 | }, 154 | [], 155 | ) 156 | self.EmitSignal(MAIN_IFACE, "DeviceAdded", "o", [path]) 157 | return path 158 | 159 | 160 | @dbus.service.method(MOCK_IFACE, in_signature="ssdx", out_signature="s") 161 | def AddChargingBattery(self, device_name, model_name, percentage, seconds_to_full): 162 | """Convenience method to add a charging battery object 163 | 164 | You have to specify a device name which must be a valid part of an object 165 | path, e. g. "mock_ac", an arbitrary model name, the charge percentage, and 166 | the seconds until the battery is full. 167 | 168 | Please note that this does not set any global properties such as 169 | "on-battery". 170 | 171 | Returns the new object path. 172 | """ 173 | path = "/org/freedesktop/UPower/devices/" + device_name 174 | self.AddObject( 175 | path, 176 | DEVICE_IFACE, 177 | { 178 | "PowerSupply": dbus.Boolean(True), 179 | "IsPresent": dbus.Boolean(True), 180 | "Model": dbus.String(model_name), 181 | "Percentage": dbus.Double(percentage), 182 | "TimeToFull": dbus.Int64(seconds_to_full), 183 | "EnergyFull": dbus.Double(100.0), 184 | "Energy": dbus.Double(percentage), 185 | # UP_DEVICE_STATE_CHARGING 186 | "State": dbus.UInt32(1), 187 | # UP_DEVICE_KIND_BATTERY 188 | "Type": dbus.UInt32(2), 189 | }, 190 | [], 191 | ) 192 | self.EmitSignal(MAIN_IFACE, "DeviceAdded", "o", [path]) 193 | return path 194 | 195 | 196 | @dbus.service.method(MOCK_IFACE, in_signature="uuddddxxbsu", out_signature="") 197 | def SetupDisplayDevice( 198 | self, 199 | _type, # noqa: RUF052, RUF100 (access to local dummy variable); but this is API now 200 | state, 201 | percentage, 202 | energy, 203 | energy_full, 204 | energy_rate, 205 | time_to_empty, 206 | time_to_full, 207 | is_present, 208 | icon_name, 209 | warning_level, 210 | ): 211 | """Convenience method to configure DisplayDevice properties 212 | 213 | This calls Set() for all properties that the DisplayDevice is defined to 214 | have, and is shorter if you have to completely set it up instead of 215 | changing just one or two properties. 216 | """ 217 | display_props = mockobject.objects[self.p_display_dev] 218 | display_props.Set(DEVICE_IFACE, "Type", dbus.UInt32(_type)) 219 | display_props.Set(DEVICE_IFACE, "State", dbus.UInt32(state)) 220 | display_props.Set(DEVICE_IFACE, "Percentage", percentage) 221 | display_props.Set(DEVICE_IFACE, "Energy", energy) 222 | display_props.Set(DEVICE_IFACE, "EnergyFull", energy_full) 223 | display_props.Set(DEVICE_IFACE, "EnergyRate", energy_rate) 224 | display_props.Set(DEVICE_IFACE, "TimeToEmpty", dbus.Int64(time_to_empty)) 225 | display_props.Set(DEVICE_IFACE, "TimeToFull", dbus.Int64(time_to_full)) 226 | display_props.Set(DEVICE_IFACE, "IsPresent", is_present) 227 | display_props.Set(DEVICE_IFACE, "IconName", icon_name) 228 | display_props.Set(DEVICE_IFACE, "WarningLevel", dbus.UInt32(warning_level)) 229 | 230 | 231 | @dbus.service.method(MOCK_IFACE, in_signature="oa{sv}", out_signature="") 232 | def SetDeviceProperties(_self, object_path, properties): 233 | """Convenience method to Set a device's properties. 234 | 235 | object_path: the device to update 236 | properties: dictionary of keys to dbus variants. 237 | 238 | Changing this property will trigger the device's PropertiesChanged signal. 239 | """ 240 | device = dbusmock.get_object(object_path) 241 | 242 | # set the properties 243 | for key, value in properties.items(): 244 | device.Set(DEVICE_IFACE, key, value) 245 | 246 | 247 | @dbus.service.method(MOCK_IFACE, in_signature="o", out_signature="") 248 | def RemoveDevice(self, device_path): 249 | self.RemoveObject(device_path) 250 | self.EmitSignal(MAIN_IFACE, "DeviceRemoved", "o", [device_path]) 251 | -------------------------------------------------------------------------------- /dbusmock/templates/upower_power_profiles_daemon.py: -------------------------------------------------------------------------------- 1 | """power-profiles-daemon >= 0.20 mock template 2 | 3 | This creates the expected methods and properties of the main 4 | org.freedesktop.UPower.PowerProfiles object. 5 | 6 | This provides the D-Bus API as of version 0.20. 7 | """ 8 | 9 | # This program is free software; you can redistribute it and/or modify it under 10 | # the terms of the GNU Lesser General Public License as published by the Free 11 | # Software Foundation; either version 3 of the License, or (at your option) any 12 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 13 | # of the license. 14 | 15 | __author__ = "Bastien Nocera" 16 | __copyright__ = """ 17 | (c) 2021, Red Hat Inc. 18 | (c) 2017 - 2024 Martin Pitt 19 | """ 20 | 21 | import dbus 22 | 23 | BUS_NAME = "org.freedesktop.UPower.PowerProfiles" 24 | MAIN_OBJ = "/org/freedesktop/UPower/PowerProfiles" 25 | MAIN_IFACE = "org.freedesktop.UPower.PowerProfiles" 26 | SYSTEM_BUS = True 27 | 28 | 29 | def hold_profile(self, profile, reason, application_id): 30 | self.cookie += 1 31 | element = {"Profile": profile, "Reason": reason, "ApplicationId": application_id} 32 | self.holds[self.cookie] = element 33 | self.props[MAIN_IFACE]["ActiveProfileHolds"] = [] 34 | for value in self.holds.values(): 35 | self.props[MAIN_IFACE]["ActiveProfileHolds"].append(value) 36 | return self.cookie 37 | 38 | 39 | def release_profile(self, cookie): 40 | self.holds.pop(cookie) 41 | self.props[MAIN_IFACE]["ActiveProfileHolds"] = [] 42 | for value in self.holds.values(): 43 | self.props[MAIN_IFACE]["ActiveProfileHolds"].append(value) 44 | if len(self.props[MAIN_IFACE]["ActiveProfileHolds"]) == 0: 45 | self.props[MAIN_IFACE]["ActiveProfileHolds"] = dbus.Array([], signature="(aa{sv})") 46 | 47 | 48 | def load(mock, parameters): 49 | # Loaded! 50 | mock.loaded = True 51 | mock.cookie = 0 52 | mock.hold_profile = hold_profile 53 | mock.release_profile = release_profile 54 | mock.holds = {} 55 | 56 | props = { 57 | "ActiveProfile": parameters.get("ActiveProfile", "balanced"), 58 | "PerformanceDegraded": parameters.get("PerformanceDegraded", ""), 59 | "Profiles": [ 60 | dbus.Dictionary({"Profile": "power-saver", "Driver": "dbusmock"}, signature="sv"), 61 | dbus.Dictionary({"Profile": "balanced", "Driver": "dbusmock"}, signature="sv"), 62 | dbus.Dictionary({"Profile": "performance", "Driver": "dbusmock"}, signature="sv"), 63 | ], 64 | "Actions": dbus.Array([], signature="s"), 65 | "ActiveProfileHolds": dbus.Array([], signature="(aa{sv})"), 66 | } 67 | mock.AddProperties(MAIN_IFACE, dbus.Dictionary(props, signature="sv")) 68 | 69 | mock.AddMethods( 70 | MAIN_IFACE, 71 | [ 72 | ("HoldProfile", "sss", "u", "ret = self.hold_profile(self, args[0], args[1], args[2])"), 73 | ("ReleaseProfile", "u", "", "self.release_profile(self, args[0])"), 74 | ], 75 | ) 76 | -------------------------------------------------------------------------------- /dbusmock/templates/urfkill.py: -------------------------------------------------------------------------------- 1 | """urfkill mock template 2 | 3 | This creates the expected methods and properties of the main 4 | urfkill object, but no devices. You can specify any property 5 | such as urfkill in "parameters". 6 | """ 7 | 8 | # This program is free software; you can redistribute it and/or modify it under 9 | # the terms of the GNU Lesser General Public License as published by the Free 10 | # Software Foundation; either version 3 of the License, or (at your option) any 11 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 12 | # of the license. 13 | 14 | __author__ = "Jussi Pakkanen" 15 | __copyright__ = """ 16 | (C) 2015 Canonical ltd 17 | (c) 2017 - 2022 Martin Pitt 18 | """ 19 | 20 | import dbus 21 | 22 | import dbusmock 23 | 24 | SYSTEM_BUS = True 25 | BUS_NAME = "org.freedesktop.URfkill" 26 | MAIN_OBJ = "/org/freedesktop/URfkill" 27 | 28 | MAIN_IFACE = "org.freedesktop.URfkill" 29 | 30 | individual_objects = ["BLUETOOTH", "FM", "GPS", "NFC", "UWB", "WIMAX", "WLAN", "WWAN"] 31 | type2objectname = { 32 | 1: "WLAN", 33 | 2: "BLUETOOTH", 34 | 3: "UWB", 35 | 4: "WIMAX", 36 | 5: "WWAN", 37 | 6: "GPS", 38 | 7: "FM", 39 | } 40 | 41 | KS_NOTAVAILABLE = -1 42 | KS_UNBLOCKED = 0 43 | KS_SOFTBLOCKED = 1 44 | KS_HARDBLOCKED = 2 45 | 46 | 47 | def toggle_flight_mode(self, new_block_state): 48 | new_block_state = bool(new_block_state) 49 | if self.flight_mode == new_block_state: 50 | return True 51 | self.flight_mode = new_block_state 52 | for i in individual_objects: 53 | old_value = self.internal_states[i] 54 | if old_value == 1: 55 | continue # It was already blocked so we don't need to do anything 56 | path = "/org/freedesktop/URfkill/" + i 57 | obj = dbusmock.get_object(path) 58 | if new_block_state: 59 | obj.Set("org.freedesktop.URfkill.Killswitch", "state", 1) 60 | obj.EmitSignal("org.freedesktop.URfkill.Killswitch", "StateChanged", "", []) 61 | else: 62 | obj.Set("org.freedesktop.URfkill.Killswitch", "state", 0) 63 | obj.EmitSignal("org.freedesktop.URfkill.Killswitch", "StateChanged", "", []) 64 | self.EmitSignal(MAIN_IFACE, "FlightModeChanged", "b", [self.flight_mode]) 65 | return True 66 | 67 | 68 | def block(self, index, should_block): 69 | should_block = bool(should_block) 70 | if index not in type2objectname: 71 | return False 72 | objname = type2objectname[index] 73 | new_block_state = 1 if should_block else 0 74 | if self.internal_states[objname] != new_block_state: 75 | path = "/org/freedesktop/URfkill/" + objname 76 | obj = dbusmock.get_object(path) 77 | self.internal_states[objname] = new_block_state 78 | obj.Set("org.freedesktop.URfkill.Killswitch", "state", new_block_state) 79 | obj.EmitSignal("org.freedesktop.URfkill.Killswitch", "StateChanged", "", []) 80 | return True 81 | 82 | 83 | def load(mock, parameters): 84 | mock.toggle_flight_mode = toggle_flight_mode 85 | mock.block = block 86 | mock.flight_mode = False 87 | mock.internal_states = {} 88 | for oname in individual_objects: 89 | mock.internal_states[oname] = KS_UNBLOCKED 90 | 91 | # First we create the main urfkill object. 92 | mock.AddMethods( 93 | MAIN_IFACE, 94 | [ 95 | ("IsFlightMode", "", "b", "ret = self.flight_mode"), 96 | ("FlightMode", "b", "b", "ret = self.toggle_flight_mode(self, args[0])"), 97 | ("Block", "ub", "b", "ret = self.block(self, args[0], args[1])"), 98 | ], 99 | ) 100 | 101 | mock.AddProperties( 102 | MAIN_IFACE, 103 | dbus.Dictionary( 104 | { 105 | "DaemonVersion": parameters.get("DaemonVersion", "0.6.0"), 106 | "KeyControl": parameters.get("KeyControl", True), 107 | }, 108 | signature="sv", 109 | ), 110 | ) 111 | 112 | for i in individual_objects: 113 | path = "/org/freedesktop/URfkill/" + i 114 | mock.AddObject(path, "org.freedesktop.URfkill.Killswitch", {"state": mock.internal_states[i]}, []) 115 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = "python-dbusmock" 10 | copyright = "2023 - 2025, Martin Pitt" # noqa: A001 11 | author = "Martin Pitt" 12 | 13 | try: 14 | # created by setuptools_scm 15 | from dbusmock._version import __version__ as release 16 | except ImportError: 17 | release = "0.git" 18 | 19 | # -- General configuration --------------------------------------------------- 20 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 21 | 22 | extensions = [ 23 | "sphinx.ext.autodoc", 24 | "sphinx.ext.viewcode", 25 | "sphinx.ext.intersphinx", 26 | "sphinx.ext.napoleon", 27 | "myst_parser", 28 | "autoapi.extension", 29 | ] 30 | 31 | templates_path = ["_templates"] 32 | exclude_patterns = [] 33 | 34 | # -- Options for HTML output ------------------------------------------------- 35 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 36 | 37 | html_theme = "sphinx_rtd_theme" 38 | html_static_path = ["_static"] 39 | 40 | 41 | apidoc_module_dir = "../dbusmock" 42 | apidoc_output_dir = "." 43 | apidoc_separate_modules = True 44 | apidoc_excluded_paths = ["tests"] 45 | 46 | autoapi_dirs = ["../dbusmock"] 47 | autoapi_type = "python" 48 | autoapi_member_order = "bysource" 49 | autoapi_options = [ 50 | "members", 51 | "undoc-members", 52 | "show-inheritance", 53 | "show-module-summary", 54 | "special-members", 55 | "imported-members", 56 | ] 57 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. python-dbusmock documentation master file, created by 2 | sphinx-quickstart on Mon Nov 6 19:11:55 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. include:: ../README.md 7 | :parser: myst_parser.sphinx_ 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | API Documentation 14 | 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /packaging/python-dbusmock.spec: -------------------------------------------------------------------------------- 1 | %global modname dbusmock 2 | 3 | Name: python-%{modname} 4 | Version: 0.23.0 5 | Release: 1%{?dist} 6 | Summary: Mock D-Bus objects 7 | 8 | License: LGPL-3.0-or-later 9 | URL: https://pypi.python.org/pypi/python-dbusmock 10 | Source0: https://files.pythonhosted.org/packages/source/p/%{name}/python_%{modname}-%{version}.tar.gz 11 | 12 | BuildArch: noarch 13 | BuildRequires: git 14 | BuildRequires: python3-dbus 15 | BuildRequires: python3-devel 16 | BuildRequires: python3-setuptools 17 | BuildRequires: python3-gobject 18 | BuildRequires: python3-pytest 19 | BuildRequires: dbus-x11 20 | BuildRequires: upower 21 | 22 | %global _description\ 23 | With this program/Python library you can easily create mock objects on\ 24 | D-Bus. This is useful for writing tests for software which talks to\ 25 | D-Bus services such as upower, systemd, ConsoleKit, gnome-session or\ 26 | others, and it is hard (or impossible without root privileges) to set\ 27 | the state of the real services to what you expect in your tests. 28 | 29 | %description %_description 30 | 31 | %package -n python3-dbusmock 32 | Summary: %summary (Python3) 33 | Requires: python3-dbus, python3-gobject, dbus-x11 34 | %description -n python3-dbusmock %_description 35 | 36 | %prep 37 | %autosetup -n python_%{modname}-%{version} 38 | rm -rf python-%{modname}.egg-info 39 | 40 | 41 | %build 42 | %py3_build 43 | 44 | %install 45 | %py3_install 46 | 47 | %check 48 | %{__python3} -m unittest -v 49 | 50 | %files -n python3-dbusmock 51 | %doc README.md COPYING 52 | %{python3_sitelib}/*%{modname}* 53 | 54 | %changelog 55 | -------------------------------------------------------------------------------- /packit.yaml: -------------------------------------------------------------------------------- 1 | # See the documentation for more information: 2 | # https://packit.dev/docs/configuration/ 3 | upstream_project_url: https://github.com/martinpitt/python-dbusmock 4 | # HACK: should work without, but propose_downstream fails; https://github.com/packit/packit-service/issues/1511 5 | specfile_path: packaging/python-dbusmock.spec 6 | issue_repository: https://github.com/martinpitt/python-dbusmock 7 | copy_upstream_release_description: true 8 | upstream_package_name: python-dbusmock 9 | downstream_package_name: python-dbusmock 10 | 11 | actions: 12 | create-archive: 13 | - python3 -m build --sdist 14 | # spec and tarball need to be in the same directory 15 | - sh -c 'mv dist/*.tar.* packaging; ls packaging/python_dbusmock-*.tar.*' 16 | 17 | srpm_build_deps: 18 | - python3-build 19 | - python3-setuptools_scm 20 | - python3dist(wheel) 21 | 22 | jobs: 23 | - job: copr_build 24 | trigger: pull_request 25 | targets: 26 | - fedora-latest-i386 27 | - fedora-latest-aarch64 28 | - fedora-latest-ppc64le 29 | - fedora-latest-s390x 30 | - fedora-latest-armhfp 31 | # needed for tests 32 | - fedora-all 33 | - centos-stream-9-x86_64 34 | - centos-stream-10-x86_64 35 | 36 | - job: tests 37 | trigger: pull_request 38 | targets: 39 | - fedora-all 40 | - centos-stream-9-x86_64 41 | - centos-stream-10-x86_64 42 | 43 | - job: propose_downstream 44 | trigger: release 45 | dist_git_branches: 46 | - fedora-all 47 | 48 | - job: koji_build 49 | trigger: commit 50 | dist_git_branches: 51 | - fedora-all 52 | 53 | - job: bodhi_update 54 | trigger: commit 55 | dist_git_branches: 56 | # rawhide updates are created automatically 57 | - fedora-branched 58 | -------------------------------------------------------------------------------- /plans/all.fmf: -------------------------------------------------------------------------------- 1 | summary: 2 | Run all tests 3 | discover: 4 | how: fmf 5 | execute: 6 | how: tmt 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 45", "setuptools-scm"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "python-dbusmock" 7 | description = "Mock D-Bus objects" 8 | readme = "README.md" 9 | license = { file = "COPYING" } 10 | authors = [ 11 | { name = "Martin Pitt", email = "martin@piware.de" }, 12 | ] 13 | classifiers = [ 14 | "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", 15 | "Programming Language :: Python :: 3", 16 | "Development Status :: 6 - Mature", 17 | "Operating System :: POSIX :: Linux", 18 | "Operating System :: POSIX :: BSD", 19 | "Operating System :: Unix", 20 | "Topic :: Software Development :: Quality Assurance", 21 | "Topic :: Software Development :: Testing", 22 | "Topic :: Software Development :: Testing :: Mocking", 23 | "Topic :: Software Development :: Testing :: Unit", 24 | "Topic :: Software Development :: Libraries :: Python Modules", 25 | ] 26 | dynamic = ["version"] 27 | requires-python = ">=3.8" 28 | 29 | dependencies = ["dbus-python"] 30 | 31 | [project.urls] 32 | homepage = "https://github.com/martinpitt/python-dbusmock" 33 | 34 | [tool.setuptools] 35 | packages = ["dbusmock", "dbusmock.templates"] 36 | 37 | [tool.setuptools_scm] 38 | write_to = "dbusmock/_version.py" 39 | write_to_template = """ 40 | __version__ = "{version}" 41 | """ 42 | version_scheme = 'post-release' 43 | 44 | [tool.pylint] 45 | format = { max-line-length = 130 } 46 | "messages control" = { disable = ["invalid-name", "too-many-arguments", "too-many-positional-arguments"] } 47 | design = { max-args = 7, max-locals = 25, max-public-methods = 25 } 48 | 49 | [tool.mypy] 50 | warn_unused_configs = true 51 | [[tool.mypy.overrides]] 52 | module = ["dbus.*", "gi.repository", "dbusmock._version"] 53 | ignore_missing_imports = true 54 | 55 | [tool.ruff] 56 | line-length = 130 57 | preview = true 58 | 59 | [tool.ruff.lint] 60 | select = [ 61 | "A", # flake8-builtins 62 | "B", # flake8-bugbear 63 | "C4", # flake8-comprehensions 64 | "DTZ", # flake8-datetimez 65 | "E", # pycodestyle 66 | "EXE", # flake8-executable 67 | "F", # pyflakes 68 | "G", # flake8-logging-format 69 | "I", # isort 70 | "ICN", # flake8-import-conventions 71 | "ISC", # flake8-implicit-str-concat 72 | "PIE", # unnecessary type wrappers 73 | "PLE", # pylint errors 74 | "PGH", # pygrep-hooks 75 | "PYI", # https://pypi.org/project/flake8-pyi/ type hints 76 | "RET", # flake8-return 77 | "RSE", # flake8-raise 78 | "RUF", # ruff rules 79 | "SIM", # flake8-simplify 80 | "T10", # flake8-debugger 81 | "TCH", # flake8-type-checking 82 | "UP", # pyupgrade, e.g. f-string checks 83 | "W", # warnings (mostly whitespace) 84 | "YTT", # flake8-2020 85 | ] 86 | 87 | ignore = [ 88 | "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` 89 | "SIM105", # Use `contextlib.suppress(KeyError)` instead of `try`-`except`-`pass` 90 | ] 91 | 92 | [tool.black] 93 | line-length = 118 94 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = python-dbusmock 3 | version = attr: dbusmock.__version__ 4 | description = Mock D-Bus objects 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | author = Martin Pitt 8 | author_email = martin@piware.de 9 | url = https://github.com/martinpitt/python-dbusmock 10 | download_url = https://pypi.python.org/pypi/python-dbusmock/ 11 | license = LGPL 3+ 12 | 13 | classifiers = 14 | License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) 15 | Programming Language :: Python :: 3 16 | Development Status :: 6 - Mature 17 | Operating System :: POSIX :: Linux 18 | Operating System :: POSIX :: BSD 19 | Operating System :: Unix 20 | Topic :: Software Development :: Quality Assurance 21 | Topic :: Software Development :: Testing 22 | Topic :: Software Development :: Testing :: Mocking 23 | Topic :: Software Development :: Testing :: Unit 24 | Topic :: Software Development :: Libraries :: Python Modules 25 | 26 | [options] 27 | packages = 28 | dbusmock 29 | dbusmock.templates 30 | install_requires = 31 | dbus-python 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # this setup.py exists for the benefit of RPM builds. Doing that with PyPA build 3 | # is completely busted still. 4 | 5 | import setuptools 6 | 7 | setuptools.setup() 8 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinpitt/python-dbusmock/95b904e59c0fe8fe15cf4de47f00969b55c5725d/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | pytest_plugins = "dbusmock.pytest_fixtures" 2 | -------------------------------------------------------------------------------- /tests/main.fmf: -------------------------------------------------------------------------------- 1 | summary: smoke test 2 | duration: 1m 3 | require: 4 | - python3-dbusmock 5 | test: | 6 | set -eux -o pipefail 7 | python3 -m dbusmock com.example.Foo / com.example.Foo.Manager & 8 | MOCK=$! 9 | trap "kill $MOCK; wait $MOCK || true" EXIT INT QUIT PIPE 10 | 11 | until busctl list --user | grep -q com.example.Foo; do sleep 0.5; done 12 | 13 | busctl call --user com.example.Foo / org.freedesktop.DBus.Mock AddMethod sssss '' 'Ping' '' '' '' 14 | busctl introspect --user com.example.Foo / com.example.Foo.Manager | grep Ping 15 | -------------------------------------------------------------------------------- /tests/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eux 3 | IMAGE="$1" 4 | 5 | if type podman >/dev/null 2>&1; then 6 | RUNC=podman 7 | else 8 | RUNC="sudo docker" 9 | fi 10 | 11 | # only run static code checks on a single release, too annoying to keep the code compatible with multiple versions 12 | if [ "${IMAGE%fedora:latest}" = "$IMAGE" ]; then 13 | SKIP_STATIC_CHECKS="1" 14 | fi 15 | 16 | OS=${IMAGE##*/} 17 | OS=${OS%:*} 18 | $RUNC run --interactive -e DEBUG=${DEBUG:-} -e SKIP_STATIC_CHECKS="${SKIP_STATIC_CHECKS:-}" --rm ${RUNC_OPTIONS:-} --volume `pwd`:/source:ro --workdir /source "$IMAGE" /bin/sh tests/run-$OS 19 | -------------------------------------------------------------------------------- /tests/run-centos: -------------------------------------------------------------------------------- 1 | run-fedora -------------------------------------------------------------------------------- /tests/run-debian: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eux 3 | 4 | # go-faster apt 5 | echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/90nolanguages 6 | 7 | # upgrade 8 | export DEBIAN_FRONTEND=noninteractive 9 | apt-get update 10 | apt-get install -y eatmydata 11 | eatmydata apt-get -y --purge dist-upgrade 12 | 13 | # install build dependencies 14 | eatmydata apt-get install --no-install-recommends -y git \ 15 | python3-all python3-setuptools python3-setuptools-scm python3-build python3-venv \ 16 | python3-dbus python3-pytest python3-gi gir1.2-glib-2.0 \ 17 | dbus libnotify-bin upower network-manager bluez ofono ofono-scripts power-profiles-daemon 18 | 19 | # systemd's tools otherwise fail on "not been booted with systemd" 20 | mkdir -p /run/systemd/system 21 | 22 | # run build and test as user 23 | useradd build 24 | su -s /bin/sh - build << EOF || { [ -z "$DEBUG" ] || sleep infinity; exit 1; } 25 | set -ex 26 | export SKIP_STATIC_CHECKS="$SKIP_STATIC_CHECKS" 27 | cp -r $(pwd) /tmp/source 28 | cd /tmp/source 29 | python3 -m unittest -v 30 | python3 -m pytest -vv -k 'test_pytest or TestAPI' 31 | # massively parallel test to check for races 32 | for i in \$(seq 100); do 33 | ( PYTHONPATH=. python3 tests/test_api.py TestTemplates || touch /tmp/fail ) & 34 | done 35 | wait 36 | [ ! -e /tmp/fail ] 37 | 38 | my_version=\$(git describe --abbrev=0) 39 | 40 | # test sdist with PyPA build 41 | python3 -m build --sdist 42 | tar --wildcards --strip-components=1 -xvf dist/python_dbusmock-\${my_version}*.tar.gz '*/PKG-INFO' 43 | grep "^Version: \${my_version}" PKG-INFO 44 | grep "^## Purpose" PKG-INFO 45 | EOF 46 | -------------------------------------------------------------------------------- /tests/run-fedora: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eux 3 | # install build dependencies 4 | dnf -y install python3-setuptools python3 python3-gobject-base \ 5 | python3-dbus dbus-x11 util-linux \ 6 | upower NetworkManager ModemManager bluez libnotify polkit 7 | 8 | if ! grep -q :el /etc/os-release; then 9 | dnf -y install power-profiles-daemon iio-sensor-proxy python3-pytest 10 | else 11 | dnf -y install python3-pip 12 | pip install pytest 13 | fi 14 | 15 | if [ "$SKIP_STATIC_CHECKS" != "1" ]; then 16 | dnf -y install python3-pylint python3-mypy python3-pip black 17 | pip install ruff 18 | fi 19 | 20 | # systemd's tools otherwise fail on "not been booted with systemd" 21 | mkdir -p /run/systemd/system 22 | 23 | # run build and test as user 24 | useradd build 25 | su -s /bin/sh - build << EOF || { [ -z "$DEBUG" ] || sleep infinity; exit 1; } 26 | set -ex 27 | cd /source 28 | export SKIP_STATIC_CHECKS="$SKIP_STATIC_CHECKS" 29 | 30 | python3 -m unittest -v 31 | python3 -m pytest -v 32 | EOF 33 | -------------------------------------------------------------------------------- /tests/run-ubuntu: -------------------------------------------------------------------------------- 1 | run-debian -------------------------------------------------------------------------------- /tests/test_api_pytest.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2023 Martin Pitt 10 | """ 11 | 12 | import subprocess 13 | 14 | import pytest 15 | 16 | import dbusmock 17 | 18 | 19 | def test_dbusmock_test(dbusmock_session): 20 | assert dbusmock_session 21 | test_iface = "org.freedesktop.Test.Main" 22 | 23 | with dbusmock.SpawnedMock.spawn_for_name("org.freedesktop.Test", "/", test_iface) as server: 24 | obj_test = server.obj 25 | obj_test.AddMethod("", "Upper", "s", "s", "ret = args[0].upper()", interface_name=dbusmock.MOCK_IFACE) 26 | assert obj_test.Upper("hello", interface=test_iface) == "HELLO" 27 | 28 | 29 | @pytest.fixture(name="upower_mock") 30 | def fixture_upower_mock(dbusmock_system): 31 | assert dbusmock_system 32 | with dbusmock.SpawnedMock.spawn_with_template("upower") as server: 33 | yield server.obj 34 | 35 | 36 | def test_dbusmock_test_template(upower_mock): 37 | assert upower_mock 38 | out = subprocess.check_output(["upower", "--dump"], text=True) 39 | assert "version:" in out 40 | assert "0.99" in out 41 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2012 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import importlib.util 14 | import shutil 15 | import subprocess 16 | import sys 17 | import tempfile 18 | import tracemalloc 19 | import unittest 20 | from pathlib import Path 21 | 22 | import dbus 23 | 24 | import dbusmock 25 | 26 | tracemalloc.start(25) 27 | have_upower = shutil.which("upower") 28 | have_gdbus = shutil.which("gdbus") 29 | 30 | 31 | class TestCLI(dbusmock.DBusTestCase): 32 | """Test running dbusmock from the command line""" 33 | 34 | @classmethod 35 | def setUpClass(cls): 36 | cls.start_system_bus() 37 | cls.start_session_bus() 38 | cls.system_con = cls.get_dbus(True) 39 | cls.session_con = cls.get_dbus() 40 | 41 | def setUp(self): 42 | self.p_mock = None 43 | 44 | def tearDown(self): 45 | if self.p_mock: 46 | if self.p_mock.stdout: 47 | self.p_mock.stdout.close() 48 | if self.p_mock.stderr: 49 | self.p_mock.stdout.close() 50 | self.p_mock.terminate() 51 | self.p_mock.wait() 52 | self.p_mock = None 53 | 54 | def start_mock(self, args, wait_name, wait_path, wait_system=False): 55 | # pylint: disable=consider-using-with 56 | self.p_mock = subprocess.Popen( 57 | [sys.executable, "-m", "dbusmock", *args], stdout=subprocess.PIPE, universal_newlines=True 58 | ) 59 | self.wait_for_bus_object(wait_name, wait_path, wait_system) 60 | 61 | def start_mock_process(self, args): 62 | return subprocess.check_output([sys.executable, "-m", "dbusmock", *args], text=True) 63 | 64 | def test_session_bus(self): 65 | self.start_mock(["com.example.Test", "/", "TestIface"], "com.example.Test", "/") 66 | 67 | def test_system_bus(self): 68 | self.start_mock(["--system", "com.example.Test", "/", "TestIface"], "com.example.Test", "/", True) 69 | 70 | def test_template_upower(self): 71 | self.start_mock(["-t", "upower"], "org.freedesktop.UPower", "/org/freedesktop/UPower", True) 72 | self.check_upower_running() 73 | 74 | def test_template_upower_explicit_path(self): 75 | spec = importlib.util.find_spec("dbusmock.templates.upower") 76 | self.assertTrue(Path(spec.origin).exists()) 77 | self.start_mock(["-t", spec.origin], "org.freedesktop.UPower", "/org/freedesktop/UPower", True) 78 | self.check_upower_running() 79 | 80 | def check_upower_running(self): 81 | # check that it actually ran the template, if we have upower 82 | if have_upower: 83 | out = subprocess.check_output(["upower", "--dump"], text=True) 84 | self.assertRegex(out, r"on-battery:\s+no") 85 | 86 | mock_out = self.p_mock.stdout.readline() 87 | self.assertTrue("EnumerateDevices" in mock_out or "GetAll" in mock_out, mock_out) 88 | 89 | def test_template_explicit_system(self): 90 | # --system is redundant here, but should not break 91 | self.start_mock(["--system", "-t", "upower"], "org.freedesktop.UPower", "/org/freedesktop/UPower", True) 92 | self.check_upower_running() 93 | 94 | def test_template_override_session(self): 95 | self.start_mock(["--session", "-t", "upower"], "org.freedesktop.UPower", "/org/freedesktop/UPower", False) 96 | 97 | def test_template_conflicting_bus(self): 98 | with self.assertRaises(subprocess.CalledProcessError) as cm: 99 | subprocess.check_output( 100 | [sys.executable, "-m", "dbusmock", "--system", "--session", "-t", "upower"], 101 | stderr=subprocess.STDOUT, 102 | text=True, 103 | ) 104 | err = cm.exception 105 | self.assertEqual(err.returncode, 2) 106 | self.assertRegex(err.output, "--system.*--session.*exclusive") 107 | 108 | def test_template_parameters(self): 109 | self.start_mock( 110 | ["-t", "upower", "-p", '{"DaemonVersion": "0.99.0", "OnBattery": true}'], 111 | "org.freedesktop.UPower", 112 | "/org/freedesktop/UPower", 113 | True, 114 | ) 115 | 116 | # check that it actually ran the template, if we have upower 117 | if have_upower: 118 | out = subprocess.check_output(["upower", "--dump"], text=True) 119 | self.assertRegex(out, r"daemon-version:\s+0\.99\.0") 120 | self.assertRegex(out, r"on-battery:\s+yes") 121 | 122 | def test_template_parameters_malformed_json(self): 123 | with self.assertRaises(subprocess.CalledProcessError) as cm: 124 | subprocess.check_output( 125 | [sys.executable, "-m", "dbusmock", "-t", "upower", "-p", '{"DaemonVersion: "0.99.0"}'], 126 | stderr=subprocess.STDOUT, 127 | text=True, 128 | ) 129 | err = cm.exception 130 | self.assertEqual(err.returncode, 2) 131 | self.assertRegex(err.output, "Malformed JSON given for parameters:.* delimiter") 132 | 133 | def test_template_parameters_not_dict(self): 134 | with self.assertRaises(subprocess.CalledProcessError) as cm: 135 | subprocess.check_output( 136 | [sys.executable, "-m", "dbusmock", "-t", "upower", "-p", '"banana"'], 137 | stderr=subprocess.STDOUT, 138 | text=True, 139 | ) 140 | err = cm.exception 141 | self.assertEqual(err.returncode, 2) 142 | self.assertEqual(err.output, "JSON parameters must be a dictionary\n") 143 | 144 | @unittest.skipIf(not have_upower, "No upower installed") 145 | def test_template_upower_exec(self): 146 | out = self.start_mock_process(["-t", "upower", "--exec", "upower", "--dump"]) 147 | self.assertRegex(out, r"on-battery:\s+no") 148 | self.assertRegex(out, r"daemon-version:\s+0\.99") 149 | 150 | @unittest.skipIf(not have_gdbus, "No gdbus installed") 151 | def test_manual_upower_exec(self): 152 | out = self.start_mock_process( 153 | [ 154 | "--system", 155 | "org.freedesktop.UPower", 156 | "/org/freedesktop/UPower", 157 | "org.freedesktop.UPower", 158 | "--exec", 159 | "gdbus", 160 | "introspect", 161 | "--system", 162 | "--dest", 163 | "org.freedesktop.UPower", 164 | "--object-path", 165 | "/org/freedesktop/UPower", 166 | ] 167 | ) 168 | self.assertRegex(out, r"AddMethod\(") 169 | self.assertRegex(out, r"AddMethods\(") 170 | 171 | def test_template_local(self): 172 | with tempfile.NamedTemporaryFile(prefix="answer_", suffix=".py") as my_template: 173 | my_template.write( 174 | b"""import dbus 175 | BUS_NAME = 'universe.Ultimate' 176 | MAIN_OBJ = '/' 177 | MAIN_IFACE = 'universe.Ultimate' 178 | SYSTEM_BUS = False 179 | 180 | def load(mock, parameters): 181 | mock.AddMethods(MAIN_IFACE, [('Answer', '', 'i', 'ret = 42')]) 182 | """ 183 | ) 184 | my_template.flush() 185 | # template specifies session bus 186 | self.start_mock(["-t", my_template.name], "universe.Ultimate", "/", False) 187 | 188 | obj = self.session_con.get_object("universe.Ultimate", "/") 189 | if_u = dbus.Interface(obj, "universe.Ultimate") 190 | self.assertEqual(if_u.Answer(), 42) 191 | 192 | def test_template_override_system(self): 193 | with tempfile.NamedTemporaryFile(prefix="answer_", suffix=".py") as my_template: 194 | my_template.write( 195 | b"""import dbus 196 | BUS_NAME = 'universe.Ultimate' 197 | MAIN_OBJ = '/' 198 | MAIN_IFACE = 'universe.Ultimate' 199 | SYSTEM_BUS = False 200 | 201 | def load(mock, parameters): 202 | mock.AddMethods(MAIN_IFACE, [('Answer', '', 'i', 'ret = 42')]) 203 | """ 204 | ) 205 | my_template.flush() 206 | # template specifies session bus, but CLI overrides to system 207 | self.start_mock(["--system", "-t", my_template.name], "universe.Ultimate", "/", True) 208 | 209 | obj = self.system_con.get_object("universe.Ultimate", "/") 210 | if_u = dbus.Interface(obj, "universe.Ultimate") 211 | self.assertEqual(if_u.Answer(), 42) 212 | 213 | def test_object_manager(self): 214 | self.start_mock(["-m", "com.example.Test", "/", "TestIface"], "com.example.Test", "/") 215 | 216 | obj = self.session_con.get_object("com.example.Test", "/") 217 | if_om = dbus.Interface(obj, dbusmock.OBJECT_MANAGER_IFACE) 218 | self.assertEqual(if_om.GetManagedObjects(), {}) 219 | 220 | # add a new object, should appear 221 | obj.AddObject("/a/b", "org.Test", {"name": "foo"}, dbus.Array([], signature="(ssss)")) 222 | 223 | self.assertEqual(if_om.GetManagedObjects(), {"/a/b": {"org.Test": {"name": "foo"}}}) 224 | 225 | def test_no_args(self): 226 | with subprocess.Popen( 227 | [sys.executable, "-m", "dbusmock"], 228 | stdout=subprocess.PIPE, 229 | stderr=subprocess.PIPE, 230 | universal_newlines=True, 231 | ) as p: 232 | (out, err) = p.communicate() 233 | self.assertEqual(out, "") 234 | self.assertIn("must specify NAME", err) 235 | self.assertNotEqual(p.returncode, 0) 236 | 237 | def test_help(self): 238 | with subprocess.Popen( 239 | [sys.executable, "-m", "dbusmock", "--help"], 240 | stdout=subprocess.PIPE, 241 | stderr=subprocess.PIPE, 242 | universal_newlines=True, 243 | ) as p: 244 | (out, err) = p.communicate() 245 | self.assertEqual(err, "") 246 | self.assertIn("INTERFACE", out) 247 | self.assertIn("--system", out) 248 | self.assertEqual(p.returncode, 0) 249 | 250 | 251 | if __name__ == "__main__": 252 | # avoid writing to stderr 253 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 254 | -------------------------------------------------------------------------------- /tests/test_code.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2012 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import glob 14 | import importlib.util 15 | import os 16 | import subprocess 17 | import sys 18 | import unittest 19 | 20 | 21 | @unittest.skipIf( 22 | os.getenv("SKIP_STATIC_CHECKS", "0") == "1", "$SKIP_STATIC_CHECKS set, not running static code checks" 23 | ) 24 | class StaticCodeTests(unittest.TestCase): 25 | @unittest.skipUnless(importlib.util.find_spec("pylint"), "pylint not available, skipping") 26 | def test_pylint(self): 27 | subprocess.check_call([sys.executable, "-m", "pylint", *glob.glob("dbusmock/*.py")]) 28 | # signatures/arguments are not determined by us, docstrings are a bit pointless, and code repetition 29 | # is impractical to avoid (e.g. bluez4 and bluez5) 30 | subprocess.check_call( 31 | [ 32 | sys.executable, 33 | "-m", 34 | "pylint", 35 | "--score=n", 36 | "--disable=missing-function-docstring,R0801", 37 | "--disable=too-many-arguments,too-many-instance-attributes", 38 | "--disable=too-few-public-methods", 39 | "dbusmock/templates/", 40 | ] 41 | ) 42 | subprocess.check_call( 43 | [ 44 | sys.executable, 45 | "-m", 46 | "pylint", 47 | "--score=n", 48 | "--disable=missing-module-docstring,missing-class-docstring", 49 | "--disable=missing-function-docstring", 50 | "--disable=too-many-public-methods,too-many-lines,too-many-statements,R0801", 51 | "--disable=fixme", 52 | "tests/", 53 | ] 54 | ) 55 | 56 | @unittest.skipUnless(importlib.util.find_spec("mypy"), "mypy not available, skipping") 57 | def test_types(self): 58 | subprocess.check_call([sys.executable, "-m", "mypy", "dbusmock/", "tests/"]) 59 | 60 | def test_ruff(self): 61 | try: 62 | subprocess.check_call(["ruff", "check", "--no-cache", "."]) 63 | except FileNotFoundError: 64 | self.skipTest("ruff not available, skipping") 65 | 66 | def test_black(self): 67 | try: 68 | subprocess.check_call(["black", "--check", "--diff", "."]) 69 | except FileNotFoundError: 70 | self.skipTest("black not available, skipping") 71 | 72 | 73 | if __name__ == "__main__": 74 | # avoid writing to stderr 75 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 76 | -------------------------------------------------------------------------------- /tests/test_gnome_screensaver.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2013 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import fcntl 14 | import os 15 | import subprocess 16 | import sys 17 | import unittest 18 | 19 | import dbusmock 20 | 21 | 22 | class TestGnomeScreensaver(dbusmock.DBusTestCase): 23 | """Test mocking gnome-screensaver""" 24 | 25 | @classmethod 26 | def setUpClass(cls): 27 | cls.start_session_bus() 28 | cls.dbus_con = cls.get_dbus(False) 29 | 30 | def setUp(self): 31 | (self.p_mock, self.obj_ss) = self.spawn_server_template("gnome_screensaver", {}, stdout=subprocess.PIPE) 32 | # set log to nonblocking 33 | flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) 34 | fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) 35 | 36 | def tearDown(self): 37 | self.p_mock.stdout.close() 38 | self.p_mock.terminate() 39 | self.p_mock.wait() 40 | 41 | def test_default_state(self): 42 | """Not locked by default""" 43 | 44 | self.assertEqual(self.obj_ss.GetActive(), False) 45 | 46 | def test_lock(self): 47 | """Lock()""" 48 | 49 | self.obj_ss.Lock() 50 | self.assertEqual(self.obj_ss.GetActive(), True) 51 | self.assertGreater(self.obj_ss.GetActiveTime(), 0) 52 | 53 | self.assertRegex( 54 | self.p_mock.stdout.read(), b"emit /org/gnome/ScreenSaver org.gnome.ScreenSaver.ActiveChanged True\n" 55 | ) 56 | 57 | def test_set_active(self): 58 | """SetActive()""" 59 | 60 | self.obj_ss.SetActive(True) 61 | self.assertEqual(self.obj_ss.GetActive(), True) 62 | self.assertRegex( 63 | self.p_mock.stdout.read(), b"emit /org/gnome/ScreenSaver org.gnome.ScreenSaver.ActiveChanged True\n" 64 | ) 65 | 66 | self.obj_ss.SetActive(False) 67 | self.assertEqual(self.obj_ss.GetActive(), False) 68 | self.assertRegex( 69 | self.p_mock.stdout.read(), b"emit /org/gnome/ScreenSaver org.gnome.ScreenSaver.ActiveChanged False\n" 70 | ) 71 | 72 | 73 | if __name__ == "__main__": 74 | # avoid writing to stderr 75 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 76 | -------------------------------------------------------------------------------- /tests/test_gsd_rfkill.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Guido Günther" 8 | __copyright__ = "2024 The Phosh Developers" 9 | 10 | import fcntl 11 | import os 12 | import subprocess 13 | import sys 14 | import unittest 15 | 16 | import dbus 17 | 18 | import dbusmock 19 | 20 | 21 | class TestGsdRfkill(dbusmock.DBusTestCase): 22 | """Test mocked GNOME Settings Daemon Rfkill""" 23 | 24 | @classmethod 25 | def setUpClass(cls): 26 | cls.start_session_bus() 27 | cls.dbus_con = cls.get_dbus() 28 | 29 | def setUp(self): 30 | (self.p_mock, self.p_obj) = self.spawn_server_template("gsd_rfkill", {}, stdout=subprocess.PIPE) 31 | # set log to nonblocking 32 | flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) 33 | fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) 34 | 35 | def tearDown(self): 36 | self.p_mock.stdout.close() 37 | self.p_mock.terminate() 38 | self.p_mock.wait() 39 | 40 | def test_mainobject(self): 41 | propiface = dbus.Interface(self.p_obj, dbus.PROPERTIES_IFACE) 42 | 43 | mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "AirplaneMode") 44 | self.assertEqual(mode, False) 45 | mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "HasAirplaneMode") 46 | self.assertEqual(mode, True) 47 | 48 | def test_airplane_mode(self): 49 | propiface = dbus.Interface(self.p_obj, dbus.PROPERTIES_IFACE) 50 | 51 | self.p_obj.SetAirplaneMode(True) 52 | 53 | mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "AirplaneMode") 54 | self.assertEqual(mode, True) 55 | mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "BluetoothAirplaneMode") 56 | self.assertEqual(mode, True) 57 | mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "WwanAirplaneMode") 58 | self.assertEqual(mode, True) 59 | 60 | 61 | if __name__ == "__main__": 62 | # avoid writing to stderr 63 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 64 | -------------------------------------------------------------------------------- /tests/test_logind.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2013 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import re 14 | import shutil 15 | import subprocess 16 | import sys 17 | import tracemalloc 18 | import unittest 19 | from pathlib import Path 20 | 21 | import dbus 22 | 23 | import dbusmock 24 | 25 | tracemalloc.start(25) 26 | have_loginctl = shutil.which("loginctl") 27 | 28 | 29 | @unittest.skipUnless(have_loginctl, "loginctl not installed") 30 | @unittest.skipUnless(Path("/run/systemd/system").exists(), "/run/systemd/system does not exist") 31 | class TestLogind(dbusmock.DBusTestCase): 32 | """Test mocking logind""" 33 | 34 | @classmethod 35 | def setUpClass(cls): 36 | cls.start_system_bus() 37 | cls.dbus_con = cls.get_dbus(True) 38 | 39 | if have_loginctl: 40 | out = subprocess.check_output(["loginctl", "--version"], text=True) 41 | cls.version = re.search(r"(\d+)", out.splitlines()[0]).group(1) 42 | 43 | def setUp(self): 44 | self.p_mock = None 45 | 46 | def tearDown(self): 47 | if self.p_mock: 48 | self.p_mock.stdout.close() 49 | self.p_mock.terminate() 50 | self.p_mock.wait() 51 | 52 | def test_empty(self): 53 | (self.p_mock, _) = self.spawn_server_template("logind", {}, stdout=subprocess.PIPE) 54 | cmd = ["loginctl"] 55 | if self.version >= "209": 56 | cmd.append("--no-legend") 57 | out = subprocess.check_output([*cmd, "list-sessions"], text=True) 58 | self.assertEqual(out, "") 59 | 60 | out = subprocess.check_output([*cmd, "list-seats"], text=True) 61 | self.assertEqual(out, "") 62 | 63 | out = subprocess.check_output([*cmd, "list-users"], text=True) 64 | self.assertEqual(out, "") 65 | 66 | def test_session(self): 67 | (self.p_mock, obj_logind) = self.spawn_server_template("logind", {}, stdout=subprocess.PIPE) 68 | 69 | obj_logind.AddSession("c1", "seat0", 500, "joe", True) 70 | 71 | out = subprocess.check_output(["loginctl", "list-seats"], text=True) 72 | self.assertRegex(out, r"(^|\n)seat0\s+") 73 | 74 | out = subprocess.check_output(["loginctl", "show-seat", "seat0"], text=True) 75 | self.assertRegex(out, "Id=seat0") 76 | if self.version <= "208": 77 | self.assertRegex(out, "ActiveSession=c1") 78 | self.assertRegex(out, "Sessions=c1") 79 | 80 | out = subprocess.check_output(["loginctl", "list-users"], text=True) 81 | self.assertRegex(out, r"(^|\n)\s*500\s+joe\s*") 82 | 83 | # note, this does an actual getpwnam() in the client, so we cannot call 84 | # this with hardcoded user names; get from actual user in the system 85 | # out = subprocess.check_output(['loginctl', 'show-user', 'joe'], 86 | # universal_newlines=True) 87 | # self.assertRegex(out, 'UID=500') 88 | # self.assertRegex(out, 'GID=500') 89 | # self.assertRegex(out, 'Name=joe') 90 | # self.assertRegex(out, 'Sessions=c1') 91 | # self.assertRegex(out, 'State=active') 92 | 93 | out = subprocess.check_output(["loginctl", "list-sessions"], text=True) 94 | self.assertRegex(out, "c1 +500 +joe +seat0") 95 | 96 | out = subprocess.check_output(["loginctl", "show-session", "c1"], text=True) 97 | self.assertRegex(out, "Id=c1") 98 | self.assertRegex(out, "Class=user") 99 | self.assertRegex(out, "Active=yes") 100 | self.assertRegex(out, "State=active") 101 | self.assertRegex(out, "Name=joe") 102 | self.assertRegex(out, "LockedHint=no") 103 | 104 | session_mock = dbus.Interface( 105 | self.dbus_con.get_object("org.freedesktop.login1", "/org/freedesktop/login1/session/c1"), 106 | "org.freedesktop.login1.Session", 107 | ) 108 | session_mock.SetLockedHint(True) 109 | 110 | out = subprocess.check_output(["loginctl", "show-session", "c1"], text=True) 111 | self.assertRegex(out, "Id=c1") 112 | self.assertRegex(out, "LockedHint=yes") 113 | 114 | def test_properties(self): 115 | (self.p_mock, obj_logind) = self.spawn_server_template("logind", {}, stdout=subprocess.PIPE) 116 | props = obj_logind.GetAll("org.freedesktop.login1.Manager", interface=dbus.PROPERTIES_IFACE) 117 | self.assertEqual(props["PreparingForSleep"], False) 118 | self.assertEqual(props["IdleSinceHint"], 0) 119 | 120 | def test_inhibit(self): 121 | (self.p_mock, obj_logind) = self.spawn_server_template("logind", {}, stdout=subprocess.PIPE) 122 | 123 | # what, who, why, mode 124 | fd = obj_logind.Inhibit("suspend", "testcode", "purpose", "delay") 125 | 126 | # Our inhibitor is held 127 | out = subprocess.check_output(["systemd-inhibit"], text=True) 128 | self.assertRegex( 129 | out.replace("\n", " "), 130 | "(testcode +[0-9]+ +[^ ]* +[0-9]+ +[^ ]* +suspend purpose delay)|" 131 | "(Who: testcode.*What: suspend.*Why: purpose.*Mode: delay.*)", 132 | ) 133 | 134 | del fd 135 | # No inhibitor is held 136 | out = subprocess.check_output(["systemd-inhibit"], text=True) 137 | self.assertRegex(out, "No inhibitors|0 inhibitors listed") 138 | 139 | 140 | if __name__ == "__main__": 141 | # avoid writing to stderr 142 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 143 | -------------------------------------------------------------------------------- /tests/test_low_memory_monitor.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Bastien Nocera" 8 | __copyright__ = """ 9 | (c) 2019 Red Hat Inc. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import fcntl 14 | import os 15 | import subprocess 16 | import sys 17 | import unittest 18 | 19 | import dbus 20 | import dbus.mainloop.glib 21 | 22 | import dbusmock 23 | 24 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 25 | 26 | 27 | class TestLowMemoryMonitor(dbusmock.DBusTestCase): 28 | """Test mocking low-memory-monitor""" 29 | 30 | @classmethod 31 | def setUpClass(cls): 32 | cls.start_system_bus() 33 | cls.dbus_con = cls.get_dbus(True) 34 | 35 | def setUp(self): 36 | (self.p_mock, self.obj_lmm) = self.spawn_server_template("low_memory_monitor", {}, stdout=subprocess.PIPE) 37 | # set log to nonblocking 38 | flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) 39 | fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) 40 | self.last_warning = -1 41 | self.dbusmock = dbus.Interface(self.obj_lmm, dbusmock.MOCK_IFACE) 42 | 43 | def tearDown(self): 44 | self.p_mock.stdout.close() 45 | self.p_mock.terminate() 46 | self.p_mock.wait() 47 | 48 | def test_low_memory_warning_signal(self): 49 | """LowMemoryWarning signal""" 50 | 51 | self.dbusmock.EmitWarning(100) 52 | log = self.p_mock.stdout.read() 53 | self.assertRegex(log, b"[0-9.]+ emit .*LowMemoryWarning 100\n") 54 | 55 | self.dbusmock.EmitWarning(255) 56 | log = self.p_mock.stdout.read() 57 | self.assertRegex(log, b"[0-9.]+ emit .*LowMemoryWarning 255\n") 58 | 59 | 60 | if __name__ == "__main__": 61 | # avoid writing to stderr 62 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 63 | -------------------------------------------------------------------------------- /tests/test_modemmanager.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Guido Günther" 8 | __copyright__ = """ 9 | (c) 2024 The Phosh Developers 10 | """ 11 | 12 | import shutil 13 | import subprocess 14 | import sys 15 | import unittest 16 | 17 | import dbus 18 | import dbus.mainloop.glib 19 | 20 | import dbusmock 21 | 22 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 23 | 24 | mmcli_has_cbm_support = False 25 | have_mmcli = shutil.which("mmcli") 26 | if have_mmcli: 27 | out = subprocess.run(["mmcli", "--help"], capture_output=True, text=True) # pylint: disable=subprocess-run-check 28 | mmcli_has_cbm_support = "--help-cell-broadcast" in out.stdout 29 | 30 | 31 | class TestModemManagerBase(dbusmock.DBusTestCase): 32 | """Test mocking ModemManager""" 33 | 34 | dbus_interface = "" 35 | 36 | @classmethod 37 | def setUpClass(cls): 38 | super().setUpClass() 39 | cls.start_system_bus() 40 | cls.dbus_con = cls.get_dbus(True) 41 | 42 | def setUp(self): 43 | super().setUp() 44 | (self.p_mock, self.p_obj) = self.spawn_server_template("modemmanager", {}, stdout=subprocess.PIPE) 45 | 46 | def tearDown(self): 47 | if self.p_mock: 48 | self.p_mock.stdout.close() 49 | self.p_mock.terminate() 50 | self.p_mock.wait() 51 | 52 | super().tearDown() 53 | 54 | def get_property(self, name): 55 | return self.p_obj.Get(self.dbus_interface, name, dbus_interface=dbus.PROPERTIES_IFACE) 56 | 57 | 58 | @unittest.skipUnless(have_mmcli, "mmcli utility not available") 59 | class TestModemManagerMmcliBase(TestModemManagerBase): 60 | """Base ModemManager interface tests using mmcli""" 61 | 62 | ret = None 63 | 64 | def run_mmcli(self, args): 65 | self.assertIsNone(self.ret) 66 | self.ret = subprocess.run( # pylint: disable=subprocess-run-check 67 | ["mmcli", *args], capture_output=True, text=True 68 | ) 69 | 70 | def assertOutputEquals(self, expected_lines): 71 | self.assertIsNotNone(self.ret) 72 | lines = self.ret.stdout.split("\n") 73 | self.assertEqual(len(lines), len(expected_lines)) 74 | for expected, line in zip(expected_lines, lines): 75 | self.assertEqual(expected, line) 76 | 77 | def assertOutputContainsLine(self, expected_line, ret=0): 78 | self.assertEqual(self.ret.returncode, ret) 79 | self.assertIn(expected_line, self.ret.stdout) 80 | 81 | 82 | class TestModemManagerModemMmcli(TestModemManagerMmcliBase): 83 | """main ModemManager interface tests using mmcli""" 84 | 85 | def test_no_modems(self): 86 | self.run_mmcli(["-m", "any"]) 87 | self.assertEqual(self.ret.returncode, 1) 88 | self.assertIn("error: couldn't find modem", self.ret.stderr) 89 | 90 | def test_modem(self): 91 | self.p_obj.AddSimpleModem() 92 | self.run_mmcli(["-m", "any"]) 93 | self.assertOutputEquals( 94 | [ 95 | " -----------------------------", 96 | " General | path: /org/freedesktop/ModemManager1/Modems/8", 97 | " -----------------------------", 98 | " Hardware | model: E1750", 99 | " | firmware revision: 11.126.08.01.00", 100 | " -----------------------------", 101 | " Status | state: enabled", 102 | " | power state: on", 103 | " | access tech: lte", 104 | " | signal quality: 70% (recent)", 105 | " -----------------------------", 106 | " Modes | supported: allowed: 4g; preferred: 4g", 107 | " | allowed: 2g, 3g; preferred: 3g", 108 | " | current: allowed: 4g; preferred: 4g", 109 | " -----------------------------", 110 | " 3GPP | imei: doesnotmatter", 111 | " | operator id: 00101", 112 | " | operator name: TheOperator", 113 | " | registration: idle", 114 | " -----------------------------", 115 | " SIM | primary sim path: /org/freedesktop/ModemManager1/SIM/2", 116 | "", 117 | ] 118 | ) 119 | 120 | def test_sim(self): 121 | self.p_obj.AddSimpleModem() 122 | self.run_mmcli(["-i", "any"]) 123 | self.assertOutputEquals( 124 | [ 125 | " --------------------", 126 | " General | path: /org/freedesktop/ModemManager1/SIM/2", 127 | " --------------------", 128 | " Properties | active: yes", 129 | " | imsi: doesnotmatter", 130 | "", 131 | ] 132 | ) 133 | 134 | def test_voice_call_list(self): 135 | self.p_obj.AddSimpleModem() 136 | self.run_mmcli(["-m", "any", "--voice-list-calls"]) 137 | self.assertOutputContainsLine("No calls were found\n") 138 | 139 | def test_voice_status(self): 140 | self.p_obj.AddSimpleModem() 141 | self.run_mmcli(["-m", "any", "--voice-status"]) 142 | self.assertOutputContainsLine("emergency only: no\n") 143 | 144 | @unittest.skipUnless(mmcli_has_cbm_support, "mmcli has no CBM suppot") 145 | def test_cbm(self): 146 | self.p_obj.AddSimpleModem() 147 | self.p_obj.AddCbm(2, 4383, "This is a test") 148 | self.run_mmcli(["-m", "any", "--cell-broadcast-list-cbm"]) 149 | self.assertOutputEquals( 150 | [ 151 | " /org/freedesktop/ModemManager1/Cbm/1 (received)", 152 | "", 153 | ] 154 | ) 155 | 156 | 157 | if __name__ == "__main__": 158 | # avoid writing to stderr 159 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 160 | -------------------------------------------------------------------------------- /tests/test_notification_daemon.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2012 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import fcntl 14 | import os 15 | import subprocess 16 | import sys 17 | import unittest 18 | 19 | import dbus 20 | 21 | import dbusmock 22 | 23 | try: 24 | notify_send_version = subprocess.check_output(["notify-send", "--version"], text=True) 25 | notify_send_version = notify_send_version.split()[-1] 26 | except (OSError, subprocess.CalledProcessError): 27 | notify_send_version = "" 28 | 29 | 30 | @unittest.skipUnless(notify_send_version, "notify-send not installed") 31 | class TestNotificationDaemon(dbusmock.DBusTestCase): 32 | """Test mocking notification-daemon""" 33 | 34 | @classmethod 35 | def setUpClass(cls): 36 | cls.start_session_bus() 37 | cls.dbus_con = cls.get_dbus(False) 38 | 39 | def setUp(self): 40 | (self.p_mock, self.obj_daemon) = self.spawn_server_template("notification_daemon", {}, stdout=subprocess.PIPE) 41 | # set log to nonblocking 42 | flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) 43 | fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) 44 | 45 | def tearDown(self): 46 | self.p_mock.stdout.close() 47 | self.p_mock.terminate() 48 | self.p_mock.wait() 49 | 50 | def test_no_options(self): 51 | """notify-send with no options""" 52 | 53 | subprocess.check_call(["notify-send", "title", "my text"]) 54 | log = self.p_mock.stdout.read() 55 | self.assertRegex(log, b'[0-9.]+ Notify "notify-send" 0 "" "title" "my text" \\[\\]') 56 | 57 | def test_options(self): 58 | """notify-send with some options""" 59 | 60 | subprocess.check_call(["notify-send", "-t", "27", "-a", "fooApp", "-i", "warning_icon", "title", "my text"]) 61 | log = self.p_mock.stdout.read() 62 | # HACK: Why is the timeout missing on s390x? 63 | if os.uname().machine != "s390x": 64 | self.assertRegex(log, rb"[0-9.]+ Notify.* 27\n") 65 | # libnotify 0.8.4 changes warning_icon from positional to optional argument, so check that separately 66 | self.assertRegex(log, rb'[0-9.]+ Notify "fooApp" 0 "(warning_icon)?" "title" "my text" \[\] {.*"urgency": 1') 67 | self.assertRegex(log, rb"[0-9.]+ Notify .*warning_icon") 68 | 69 | def test_id(self): 70 | """ID handling""" 71 | 72 | notify_proxy = dbus.Interface( 73 | self.dbus_con.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications"), 74 | "org.freedesktop.Notifications", 75 | ) 76 | 77 | # with input ID 0 it should generate new IDs 78 | id_ = notify_proxy.Notify("test", 0, "", "summary", "body", [], {}, -1) 79 | self.assertEqual(id_, 1) 80 | id_ = notify_proxy.Notify("test", 0, "", "summary", "body", [], {}, -1) 81 | self.assertEqual(id_, 2) 82 | 83 | # an existing ID should just be bounced back 84 | id_ = notify_proxy.Notify("test", 4, "", "summary", "body", [], {}, -1) 85 | self.assertEqual(id_, 4) 86 | id_ = notify_proxy.Notify("test", 1, "", "summary", "body", [], {}, -1) 87 | self.assertEqual(id_, 1) 88 | 89 | # the previous doesn't forget the counter 90 | id_ = notify_proxy.Notify("test", 0, "", "summary", "body", [], {}, -1) 91 | self.assertEqual(id_, 3) 92 | 93 | def test_close(self): 94 | """CloseNotification() and NotificationClosed() signal""" 95 | 96 | notify_proxy = dbus.Interface( 97 | self.dbus_con.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications"), 98 | "org.freedesktop.Notifications", 99 | ) 100 | 101 | id_ = notify_proxy.Notify("test", 0, "", "summary", "body", [], {}, -1) 102 | self.assertEqual(id_, 1) 103 | 104 | # known notification, should send a signal 105 | notify_proxy.CloseNotification(id_) 106 | log = self.p_mock.stdout.read() 107 | self.assertRegex(log, b"[0-9.]+ emit .*NotificationClosed 1 1\n") 108 | 109 | # unknown notification, don't send a signal 110 | notify_proxy.CloseNotification(id_ + 1) 111 | log = self.p_mock.stdout.read() 112 | self.assertNotIn(b"NotificationClosed", log) 113 | 114 | 115 | if __name__ == "__main__": 116 | # avoid writing to stderr 117 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 118 | -------------------------------------------------------------------------------- /tests/test_ofono.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2013 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import os 14 | import subprocess 15 | import sys 16 | import unittest 17 | from pathlib import Path 18 | 19 | import dbus 20 | 21 | import dbusmock 22 | 23 | script_dir = Path(os.environ.get("OFONO_SCRIPT_DIR", "/usr/share/ofono/scripts")) 24 | 25 | have_scripts = os.access(script_dir / "list-modems", os.X_OK) 26 | 27 | 28 | @unittest.skipUnless(have_scripts, "ofono scripts not available, set $OFONO_SCRIPT_DIR") 29 | class TestOfono(dbusmock.DBusTestCase): 30 | """Test mocking ofonod""" 31 | 32 | @classmethod 33 | def setUpClass(cls): 34 | cls.start_system_bus() 35 | cls.dbus_con = cls.get_dbus(True) 36 | (cls.p_mock, cls.obj_ofono) = cls.spawn_server_template("ofono", {}, stdout=subprocess.PIPE) 37 | 38 | def setUp(self): 39 | self.obj_ofono.Reset() 40 | 41 | def test_list_modems(self): 42 | """Manager.GetModems()""" 43 | 44 | out = subprocess.check_output([script_dir / "list-modems"]) 45 | self.assertTrue(out.startswith(b"[ /ril_0 ]"), out) 46 | self.assertIn(b"Powered = 1", out) 47 | self.assertIn(b"Online = 1", out) 48 | self.assertIn(b"Model = Mock Modem", out) 49 | self.assertIn(b"[ org.ofono.NetworkRegistration ]", out) 50 | self.assertIn(b"Status = registered", out) 51 | self.assertIn(b"Name = fake.tel", out) 52 | self.assertIn(b"Technology = gsm", out) 53 | self.assertIn(b"[ org.ofono.SimManager ]", out) 54 | self.assertIn(b"PinRequired = none", out) 55 | self.assertIn(b"Present = 1", out) 56 | self.assertIn(b"CardIdentifier = 893581234000000000000", out) 57 | self.assertIn(b"MobileCountryCode = 310", out) 58 | self.assertIn(b"MobileNetworkCode = 150", out) 59 | self.assertIn(b"Serial = 12345678-1234-1234-1234-000000000000", out) 60 | self.assertIn(b"SubscriberIdentity = 310150000000000", out) 61 | 62 | def test_outgoing_call(self): 63 | """outgoing voice call""" 64 | 65 | # no calls by default 66 | out = subprocess.check_output([script_dir / "list-calls"]) 67 | self.assertEqual(out, b"[ /ril_0 ]\n") 68 | 69 | # start call 70 | out = subprocess.check_output([script_dir / "dial-number", "12345"]) 71 | self.assertEqual(out, b"Using modem /ril_0\n/ril_0/voicecall01\n") 72 | 73 | out = subprocess.check_output([script_dir / "list-calls"]) 74 | self.assertIn(b"/ril_0/voicecall01", out) 75 | self.assertIn(b"LineIdentification = 12345", out) 76 | self.assertIn(b"State = dialing", out) 77 | 78 | out = subprocess.check_output([script_dir / "hangup-call", "/ril_0/voicecall01"]) 79 | self.assertEqual(out, b"") 80 | 81 | # no active calls any more 82 | out = subprocess.check_output([script_dir / "list-calls"]) 83 | self.assertEqual(out, b"[ /ril_0 ]\n") 84 | 85 | def test_hangup_all(self): 86 | """multiple outgoing voice calls""" 87 | 88 | out = subprocess.check_output([script_dir / "dial-number", "12345"]) 89 | self.assertEqual(out, b"Using modem /ril_0\n/ril_0/voicecall01\n") 90 | 91 | out = subprocess.check_output([script_dir / "dial-number", "54321"]) 92 | self.assertEqual(out, b"Using modem /ril_0\n/ril_0/voicecall02\n") 93 | 94 | out = subprocess.check_output([script_dir / "list-calls"]) 95 | self.assertIn(b"/ril_0/voicecall01", out) 96 | self.assertIn(b"/ril_0/voicecall02", out) 97 | self.assertIn(b"LineIdentification = 12345", out) 98 | self.assertIn(b"LineIdentification = 54321", out) 99 | 100 | out = subprocess.check_output([script_dir / "hangup-all"]) 101 | out = subprocess.check_output([script_dir / "list-calls"]) 102 | self.assertEqual(out, b"[ /ril_0 ]\n") 103 | 104 | def test_list_operators(self): 105 | """list operators""" 106 | 107 | out = subprocess.check_output([script_dir / "list-operators"], text=True) 108 | self.assertTrue(out.startswith("[ /ril_0 ]"), out) 109 | self.assertIn("[ /ril_0/operator/op1 ]", out) 110 | self.assertIn("Status = current", out) 111 | self.assertIn("Technologies = gsm", out) 112 | self.assertIn("MobileNetworkCode = 11", out) 113 | self.assertIn("MobileCountryCode = 777", out) 114 | self.assertIn("Name = fake.tel", out) 115 | 116 | def test_get_operators_for_two_modems(self): 117 | """Add second modem, list operators on both""" 118 | 119 | iface = "org.ofono.NetworkRegistration" 120 | 121 | # add second modem 122 | self.obj_ofono.AddModem("sim2", {"Powered": True}) 123 | 124 | # get modem proxy, get netreg interface 125 | modem_0 = self.dbus_con.get_object("org.ofono", "/ril_0") 126 | modem_0_netreg = dbus.Interface(modem_0, dbus_interface=iface) 127 | modem_0_ops = modem_0_netreg.GetOperators() 128 | 129 | # get modem proxy, get netreg interface 130 | modem_1 = self.dbus_con.get_object("org.ofono", "/sim2") 131 | modem_1_netreg = dbus.Interface(modem_1, dbus_interface=iface) 132 | modem_1_ops = modem_1_netreg.GetOperators() 133 | 134 | self.assertIn("/ril_0/operator/op1", str(modem_0_ops)) 135 | self.assertNotIn("/sim2", str(modem_0_ops)) 136 | 137 | self.assertIn("/sim2/operator/op1", str(modem_1_ops)) 138 | self.assertNotIn("/ril_0", str(modem_1_ops)) 139 | 140 | def test_second_modem(self): 141 | """Add a second modem""" 142 | out = subprocess.check_output([script_dir / "list-modems"]) 143 | self.assertIn(b"CardIdentifier = 893581234000000000000", out) 144 | self.assertIn(b"Serial = 12345678-1234-1234-1234-000000000000", out) 145 | self.assertIn(b"SubscriberIdentity = 310150000000000", out) 146 | 147 | self.obj_ofono.AddModem("sim2", {"Powered": True}) 148 | 149 | out = subprocess.check_output([script_dir / "list-modems"]) 150 | self.assertTrue(out.startswith(b"[ /ril_0 ]"), out) 151 | self.assertIn(b"[ /sim2 ]", out) 152 | self.assertIn(b"Powered = 1", out) 153 | self.assertIn(b"CardIdentifier = 893581234000000000000", out) 154 | self.assertIn(b"Serial = 12345678-1234-1234-1234-000000000000", out) 155 | self.assertIn(b"SubscriberIdentity = 310150000000000", out) 156 | self.assertIn(b"CardIdentifier = 893581234000000000001", out) 157 | self.assertIn(b"Serial = 12345678-1234-1234-1234-000000000001", out) 158 | self.assertIn(b"SubscriberIdentity = 310150000000001", out) 159 | 160 | 161 | if __name__ == "__main__": 162 | # avoid writing to stderr 163 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 164 | -------------------------------------------------------------------------------- /tests/test_polkitd.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2013 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import shutil 14 | import subprocess 15 | import sys 16 | import unittest 17 | 18 | import dbus 19 | import dbus.mainloop.glib 20 | 21 | import dbusmock 22 | 23 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 24 | have_pkcheck = shutil.which("pkcheck") 25 | 26 | 27 | @unittest.skipUnless(have_pkcheck, "pkcheck not installed") 28 | class TestPolkit(dbusmock.DBusTestCase): 29 | """Test mocking polkitd""" 30 | 31 | @classmethod 32 | def setUpClass(cls): 33 | cls.start_system_bus() 34 | cls.dbus_con = cls.get_dbus(True) 35 | 36 | def setUp(self): 37 | (self.p_mock, self.obj_polkitd) = self.spawn_server_template("polkitd", {}, stdout=subprocess.PIPE) 38 | self.dbusmock = dbus.Interface(self.obj_polkitd, dbusmock.MOCK_IFACE) 39 | 40 | def tearDown(self): 41 | self.p_mock.stdout.close() 42 | self.p_mock.terminate() 43 | self.p_mock.wait() 44 | 45 | def test_default(self): 46 | self.check_action("org.freedesktop.test.frobnicate", False) 47 | 48 | def test_allow_unknown(self): 49 | self.dbusmock.AllowUnknown(True) 50 | self.check_action("org.freedesktop.test.frobnicate", True) 51 | self.dbusmock.AllowUnknown(False) 52 | self.check_action("org.freedesktop.test.frobnicate", False) 53 | 54 | def test_set_allowed(self): 55 | self.dbusmock.SetAllowed(["org.freedesktop.test.frobnicate", "org.freedesktop.test.slap"]) 56 | self.check_action("org.freedesktop.test.frobnicate", True) 57 | self.check_action("org.freedesktop.test.slap", True) 58 | self.check_action("org.freedesktop.test.wobble", False) 59 | 60 | def test_hanging_call(self): 61 | self.dbusmock.SimulateHang(True) 62 | self.assertFalse(self.dbusmock.HaveHangingCalls()) 63 | pkcheck = self.check_action_run("org.freedesktop.test.frobnicate") 64 | with self.assertRaises(subprocess.TimeoutExpired): 65 | pkcheck.wait(0.8) 66 | 67 | self.assertTrue(self.dbusmock.HaveHangingCalls()) 68 | pkcheck.stdout.close() 69 | pkcheck.kill() 70 | pkcheck.wait() 71 | 72 | def test_hanging_call_return(self): 73 | self.dbusmock.SetAllowed(["org.freedesktop.test.frobnicate"]) 74 | self.dbusmock.SimulateHangActions(["org.freedesktop.test.frobnicate", "org.freedesktop.test.slap"]) 75 | self.assertFalse(self.dbusmock.HaveHangingCalls()) 76 | 77 | frobnicate_pkcheck = self.check_action_run("org.freedesktop.test.frobnicate") 78 | slap_pkcheck = self.check_action_run("org.freedesktop.test.slap") 79 | 80 | with self.assertRaises(subprocess.TimeoutExpired): 81 | frobnicate_pkcheck.wait(0.3) 82 | with self.assertRaises(subprocess.TimeoutExpired): 83 | slap_pkcheck.wait(0.3) 84 | 85 | self.assertTrue(self.dbusmock.HaveHangingCalls()) 86 | self.dbusmock.ReleaseHangingCalls() 87 | 88 | self.check_action_result(frobnicate_pkcheck, True) 89 | self.check_action_result(slap_pkcheck, False) 90 | 91 | def test_delayed_call(self): 92 | self.dbusmock.SetDelay(3) 93 | pkcheck = self.check_action_run("org.freedesktop.test.frobnicate") 94 | with self.assertRaises(subprocess.TimeoutExpired): 95 | pkcheck.wait(0.8) 96 | pkcheck.stdout.close() 97 | pkcheck.kill() 98 | pkcheck.wait() 99 | 100 | def test_delayed_call_return(self): 101 | self.dbusmock.SetDelay(1) 102 | self.dbusmock.SetAllowed(["org.freedesktop.test.frobnicate"]) 103 | pkcheck = self.check_action_run("org.freedesktop.test.frobnicate") 104 | with self.assertRaises(subprocess.TimeoutExpired): 105 | pkcheck.wait(0.8) 106 | self.check_action_result(pkcheck, True) 107 | 108 | @staticmethod 109 | def check_action_run(action): 110 | # pylint: disable=consider-using-with 111 | return subprocess.Popen( 112 | ["pkcheck", "--action-id", action, "--process", "123"], 113 | stdout=subprocess.PIPE, 114 | stderr=subprocess.STDOUT, 115 | universal_newlines=True, 116 | ) 117 | 118 | def check_action_result(self, pkcheck, expect_allow): 119 | out = pkcheck.communicate()[0] 120 | if expect_allow: 121 | self.assertEqual(pkcheck.returncode, 0) 122 | self.assertEqual(out, "test=test\n") 123 | else: 124 | self.assertNotEqual(pkcheck.returncode, 0) 125 | self.assertEqual(out, "test=test\nNot authorized.\n") 126 | 127 | def check_action(self, action, expect_allow): 128 | self.check_action_result(self.check_action_run(action), expect_allow) 129 | 130 | 131 | if __name__ == "__main__": 132 | # avoid writing to stderr 133 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 134 | -------------------------------------------------------------------------------- /tests/test_power_profiles_daemon.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Bastien Nocera" 8 | __copyright__ = """ 9 | (c) 2021 Red Hat Inc. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import fcntl 14 | import os 15 | import re 16 | import shutil 17 | import subprocess 18 | import sys 19 | import time 20 | import unittest 21 | 22 | import dbus 23 | import dbus.mainloop.glib 24 | 25 | import dbusmock 26 | 27 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 28 | 29 | have_powerprofilesctl = shutil.which("powerprofilesctl") 30 | 31 | 32 | @unittest.skipUnless(have_powerprofilesctl, "powerprofilesctl not installed") 33 | class TestPowerProfilesDaemon(dbusmock.DBusTestCase): 34 | """Test mocking power-profiles-daemon""" 35 | 36 | @classmethod 37 | def setUpClass(cls): 38 | cls.start_system_bus() 39 | cls.dbus_con = cls.get_dbus(True) 40 | 41 | def setUp(self): 42 | # depending on the installed client version, we need to pick the right template 43 | try: 44 | out = subprocess.run(["powerprofilesctl", "version"], capture_output=True, text=True, check=True).stdout 45 | version = re.search(r"[0-9.]+", out).group(0) 46 | version = ".".join(version.strip().split(".")[:2]) 47 | template = "power_profiles_daemon" if float(version) < 0.2 else "upower_power_profiles_daemon" 48 | except subprocess.CalledProcessError as e: 49 | # 0.20 crashes without daemon: https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/issues/139 50 | print("Failed to get powerprofilesctl version, assuming >= 0.20:", e, file=sys.stderr) 51 | template = "upower_power_profiles_daemon" 52 | 53 | (self.p_mock, self.obj_ppd) = self.spawn_server_template(template, {}, stdout=subprocess.PIPE) 54 | # set log to nonblocking 55 | flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) 56 | fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) 57 | self.dbusmock = dbus.Interface(self.obj_ppd, dbusmock.MOCK_IFACE) 58 | 59 | def tearDown(self): 60 | self.p_mock.stdout.close() 61 | self.p_mock.terminate() 62 | self.p_mock.wait() 63 | 64 | def test_list_profiles(self): 65 | """List Profiles and check active profile""" 66 | 67 | out = subprocess.check_output(["powerprofilesctl"], text=True) 68 | 69 | self.assertIn("performance:\n", out) 70 | self.assertIn("\n* balanced:\n", out) 71 | 72 | def test_change_profile(self): 73 | """Change ActiveProfile""" 74 | 75 | subprocess.check_output(["powerprofilesctl", "set", "performance"], text=True) 76 | out = subprocess.check_output(["powerprofilesctl", "get"], text=True) 77 | self.assertEqual(out, "performance\n") 78 | 79 | def run_powerprofilesctl_list_holds(self): 80 | return subprocess.check_output(["powerprofilesctl", "list-holds"], text=True) 81 | 82 | def test_list_holds(self): 83 | """Test holds""" 84 | 85 | # No holds 86 | out = self.run_powerprofilesctl_list_holds() 87 | self.assertEqual(out, "") 88 | 89 | # 1 hold 90 | # pylint: disable=consider-using-with 91 | cmd = subprocess.Popen( 92 | [ 93 | "powerprofilesctl", 94 | "launch", 95 | "-p", 96 | "power-saver", 97 | "-r", 98 | "g-s-d mock test", 99 | "-i", 100 | "org.gnome.SettingsDaemon.Power", 101 | "sleep", 102 | "60", 103 | ], 104 | stdout=subprocess.PIPE, 105 | ) 106 | time.sleep(0.3) 107 | 108 | out = self.run_powerprofilesctl_list_holds() 109 | self.assertEqual( 110 | out, 111 | ( 112 | "Hold:\n" 113 | " Profile: power-saver\n" 114 | " Application ID:" 115 | " org.gnome.SettingsDaemon.Power\n" 116 | " Reason: g-s-d mock test\n" 117 | ), 118 | ) 119 | 120 | # 2 holds 121 | # pylint: disable=consider-using-with 122 | cmd2 = subprocess.Popen( 123 | [ 124 | "powerprofilesctl", 125 | "launch", 126 | "-p", 127 | "performance", 128 | "-r", 129 | "running some game", 130 | "-i", 131 | "com.game.Game", 132 | "sleep", 133 | "60", 134 | ], 135 | stdout=subprocess.PIPE, 136 | ) 137 | out = None 138 | timeout = 2.0 139 | while timeout > 0: 140 | time.sleep(0.1) 141 | timeout -= 0.1 142 | out = self.run_powerprofilesctl_list_holds() 143 | if out != "": 144 | break 145 | else: 146 | self.fail("could not list holds") 147 | 148 | self.assertEqual( 149 | out, 150 | ( 151 | "Hold:\n" 152 | " Profile: power-saver\n" 153 | " Application ID:" 154 | " org.gnome.SettingsDaemon.Power\n" 155 | " Reason: g-s-d mock test\n\n" 156 | "Hold:\n" 157 | " Profile: performance\n" 158 | " Application ID: com.game.Game\n" 159 | " Reason: running some game\n" 160 | ), 161 | ) 162 | 163 | cmd.stdout.close() 164 | cmd.terminate() 165 | cmd.wait() 166 | 167 | cmd2.stdout.close() 168 | cmd2.terminate() 169 | cmd2.wait() 170 | 171 | def test_release_hold(self): 172 | """Test release holds""" 173 | 174 | # No holds 175 | out = self.run_powerprofilesctl_list_holds() 176 | self.assertEqual(out, "") 177 | 178 | # hold profile 179 | cookie = self.obj_ppd.HoldProfile("performance", "release test", "com.test.Test") 180 | out = self.run_powerprofilesctl_list_holds() 181 | self.assertEqual( 182 | out, 183 | ( 184 | "Hold:\n" 185 | " Profile: performance\n" 186 | " Application ID: com.test.Test\n" 187 | " Reason: release test\n" 188 | ), 189 | ) 190 | 191 | # release profile 192 | self.obj_ppd.ReleaseProfile(cookie) 193 | time.sleep(0.3) 194 | out = self.run_powerprofilesctl_list_holds() 195 | self.assertEqual(out, "") 196 | 197 | 198 | if __name__ == "__main__": 199 | # avoid writing to stderr 200 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) 201 | -------------------------------------------------------------------------------- /tests/test_systemd.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Jonas Ådahl" 8 | __copyright__ = """ 9 | (c) 2021 Red Hat 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import subprocess 14 | import sys 15 | import unittest 16 | 17 | import dbus 18 | import dbus.mainloop.glib 19 | from gi.repository import GLib 20 | 21 | import dbusmock 22 | 23 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 24 | 25 | 26 | class TestSystemd(dbusmock.DBusTestCase): 27 | """Test mocking systemd""" 28 | 29 | @classmethod 30 | def setUpClass(cls): 31 | cls.start_session_bus() 32 | cls.start_system_bus() 33 | cls.session_bus = cls.get_dbus(False) 34 | cls.system_bus = cls.get_dbus(True) 35 | 36 | def setUp(self): 37 | self.p_mock = None 38 | 39 | def tearDown(self): 40 | if self.p_mock: 41 | self.p_mock.stdout.close() 42 | self.p_mock.terminate() 43 | self.p_mock.wait() 44 | 45 | def _assert_unit_property(self, unit_obj, name, expect): 46 | value = unit_obj.Get("org.freedesktop.systemd1.Unit", name) 47 | self.assertEqual(str(value), expect) 48 | 49 | def _test_base(self, bus, system_bus=True): 50 | dummy_service = "dummy-dbusmock.service" 51 | 52 | (self.p_mock, obj_systemd) = self.spawn_server_template("systemd", {}, subprocess.PIPE, system_bus=system_bus) 53 | 54 | systemd_mock = dbus.Interface(obj_systemd, dbusmock.MOCK_IFACE) 55 | systemd_mock.AddMockUnit(dummy_service) 56 | 57 | main_loop = GLib.MainLoop() 58 | 59 | removed_jobs = [] 60 | 61 | def catch_job_removed(*args, **kwargs): 62 | if kwargs["interface"] == "org.freedesktop.systemd1.Manager" and kwargs["member"] == "JobRemoved": 63 | job_path = str(args[1]) 64 | removed_jobs.append(job_path) 65 | main_loop.quit() 66 | 67 | def wait_for_job(path): 68 | while True: 69 | main_loop.run() 70 | if path in removed_jobs: 71 | break 72 | 73 | bus.add_signal_receiver( 74 | catch_job_removed, interface_keyword="interface", path_keyword="path", member_keyword="member" 75 | ) 76 | 77 | unit_path = obj_systemd.GetUnit(dummy_service) 78 | 79 | unit_obj = bus.get_object("org.freedesktop.systemd1", unit_path) 80 | 81 | self._assert_unit_property(unit_obj, "Id", dummy_service) 82 | self._assert_unit_property(unit_obj, "LoadState", "loaded") 83 | self._assert_unit_property(unit_obj, "ActiveState", "inactive") 84 | 85 | job_path = obj_systemd.StartUnit(dummy_service, "fail") 86 | 87 | wait_for_job(job_path) 88 | self._assert_unit_property(unit_obj, "ActiveState", "active") 89 | 90 | job_path = obj_systemd.StopUnit(dummy_service, "fail") 91 | 92 | wait_for_job(job_path) 93 | self._assert_unit_property(unit_obj, "ActiveState", "inactive") 94 | 95 | self.p_mock.stdout.close() 96 | self.p_mock.terminate() 97 | self.p_mock.wait() 98 | self.p_mock = None 99 | 100 | def test_user(self): 101 | self._test_base(self.session_bus, system_bus=False) 102 | 103 | def test_system(self): 104 | self._test_base(self.system_bus, system_bus=True) 105 | 106 | 107 | if __name__ == "__main__": 108 | # avoid writing to stderr 109 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 110 | -------------------------------------------------------------------------------- /tests/test_timedated.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Iain Lane" 8 | __copyright__ = """ 9 | (c) 2013 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import shutil 14 | import subprocess 15 | import sys 16 | import unittest 17 | from pathlib import Path 18 | 19 | import dbusmock 20 | 21 | # timedatectl keeps changing its CLI output 22 | TIMEDATECTL_NTP_LABEL = "(NTP enabled|synchronized|systemd-timesyncd.service active)" 23 | 24 | have_timedatectl = shutil.which("timedatectl") 25 | 26 | 27 | @unittest.skipUnless(have_timedatectl, "timedatectl not installed") 28 | @unittest.skipUnless(Path("/run/systemd/system").exists(), "/run/systemd/system does not exist") 29 | class TestTimedated(dbusmock.DBusTestCase): 30 | """Test mocking timedated""" 31 | 32 | @classmethod 33 | def setUpClass(cls): 34 | cls.start_system_bus() 35 | cls.dbus_con = cls.get_dbus(True) 36 | 37 | def setUp(self): 38 | (self.p_mock, _) = self.spawn_server_template("timedated", {}, stdout=subprocess.PIPE) 39 | self.obj_timedated = self.dbus_con.get_object("org.freedesktop.timedate1", "/org/freedesktop/timedate1") 40 | 41 | def tearDown(self): 42 | if self.p_mock: 43 | self.p_mock.stdout.close() 44 | self.p_mock.terminate() 45 | self.p_mock.wait() 46 | 47 | def run_timedatectl(self): 48 | return subprocess.check_output(["timedatectl"], text=True) 49 | 50 | def test_default_timezone(self): 51 | out = self.run_timedatectl() 52 | # timedatectl doesn't get the timezone offset information over dbus so 53 | # we can't mock that. 54 | self.assertRegex(out, "Time *zone: Etc/Utc") 55 | 56 | def test_changing_timezone(self): 57 | self.obj_timedated.SetTimezone("Africa/Johannesburg", False) 58 | out = self.run_timedatectl() 59 | # timedatectl doesn't get the timezone offset information over dbus so 60 | # we can't mock that. 61 | self.assertRegex(out, "Time *zone: Africa/Johannesburg") 62 | 63 | def test_default_ntp(self): 64 | out = self.run_timedatectl() 65 | self.assertRegex(out, f"{TIMEDATECTL_NTP_LABEL}: yes") 66 | 67 | def test_changing_ntp(self): 68 | self.obj_timedated.SetNTP(False, False) 69 | out = self.run_timedatectl() 70 | self.assertRegex(out, f"{TIMEDATECTL_NTP_LABEL}: no") 71 | 72 | def test_default_local_rtc(self): 73 | out = self.run_timedatectl() 74 | self.assertRegex(out, "RTC in local TZ: no") 75 | 76 | def test_changing_local_rtc(self): 77 | self.obj_timedated.SetLocalRTC(True, False, False) 78 | out = self.run_timedatectl() 79 | self.assertRegex(out, "RTC in local TZ: yes") 80 | 81 | 82 | if __name__ == "__main__": 83 | # avoid writing to stderr 84 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 85 | -------------------------------------------------------------------------------- /tests/test_upower.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Martin Pitt" 8 | __copyright__ = """ 9 | (c) 2012 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import fcntl 14 | import os 15 | import shutil 16 | import subprocess 17 | import sys 18 | import time 19 | import tracemalloc 20 | import unittest 21 | 22 | import dbus 23 | 24 | import dbusmock 25 | 26 | UP_DEVICE_LEVEL_UNKNOWN = 0 27 | UP_DEVICE_LEVEL_NONE = 1 28 | 29 | tracemalloc.start(25) 30 | have_upower = shutil.which("upower") 31 | 32 | 33 | @unittest.skipUnless(have_upower, "upower not installed") 34 | class TestUPower(dbusmock.DBusTestCase): 35 | """Test mocking upowerd""" 36 | 37 | @classmethod 38 | def setUpClass(cls): 39 | cls.start_system_bus() 40 | cls.dbus_con = cls.get_dbus(True) 41 | 42 | def setUp(self): 43 | (self.p_mock, self.obj_upower) = self.spawn_server_template( 44 | "upower", 45 | { 46 | "OnBattery": True, 47 | "HibernateAllowed": False, 48 | "GetCriticalAction": "Suspend", 49 | }, 50 | stdout=subprocess.PIPE, 51 | ) 52 | # set log to nonblocking 53 | flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) 54 | fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) 55 | self.dbusmock = dbus.Interface(self.obj_upower, dbusmock.MOCK_IFACE) 56 | 57 | def tearDown(self): 58 | self.p_mock.stdout.close() 59 | self.p_mock.terminate() 60 | self.p_mock.wait() 61 | 62 | def test_no_devices(self): 63 | out = subprocess.check_output(["upower", "--dump"], text=True) 64 | self.assertIn("/DisplayDevice\n", out) 65 | # should not have any other device 66 | for line in out.splitlines(): 67 | if line.endswith("/DisplayDevice"): 68 | continue 69 | self.assertNotIn("Device", line) 70 | self.assertRegex(out, "on-battery:\\s+yes") 71 | self.assertRegex(out, "lid-is-present:\\s+yes") 72 | self.assertRegex(out, "daemon-version:\\s+0.99") 73 | self.assertRegex(out, "critical-action:\\s+Suspend") 74 | self.assertNotIn("can-suspend", out) 75 | 76 | def test_one_ac(self): 77 | path = self.dbusmock.AddAC("mock_AC", "Mock AC") 78 | self.assertEqual(path, "/org/freedesktop/UPower/devices/mock_AC") 79 | 80 | self.assertRegex( 81 | self.p_mock.stdout.read(), 82 | b'emit /org/freedesktop/UPower org.freedesktop.UPower.DeviceAdded "/org/freedesktop/UPower/devices/mock_AC"\n', 83 | ) 84 | 85 | out = subprocess.check_output(["upower", "--dump"], text=True) 86 | self.assertRegex(out, "Device: " + path) 87 | # note, Add* is not magic: this just adds an object, not change 88 | # properties 89 | self.assertRegex(out, "on-battery:\\s+yes") 90 | self.assertRegex(out, "lid-is-present:\\s+yes") 91 | # print('--------- out --------\n%s\n------------' % out) 92 | 93 | with subprocess.Popen(["upower", "--monitor-detail"], stdout=subprocess.PIPE, universal_newlines=True) as mon: 94 | time.sleep(0.3) 95 | self.dbusmock.SetDeviceProperties(path, {"PowerSupply": dbus.Boolean(True)}) 96 | time.sleep(0.2) 97 | 98 | mon.terminate() 99 | out = mon.communicate()[0] 100 | self.assertRegex(out, "device changed:\\s+" + path) 101 | # print('--------- monitor out --------\n%s\n------------' % out) 102 | 103 | def test_discharging_battery(self): 104 | path = self.dbusmock.AddDischargingBattery("mock_BAT", "Mock Battery", 30.0, 1200) 105 | self.assertEqual(path, "/org/freedesktop/UPower/devices/mock_BAT") 106 | 107 | self.assertRegex( 108 | self.p_mock.stdout.read(), 109 | b'emit /org/freedesktop/UPower org.freedesktop.UPower.DeviceAdded "/org/freedesktop/UPower/devices/mock_BAT"\n', 110 | ) 111 | 112 | out = subprocess.check_output(["upower", "--dump"], text=True) 113 | self.assertRegex(out, "Device: " + path) 114 | # note, Add* is not magic: this just adds an object, not change 115 | # properties 116 | self.assertRegex(out, "on-battery:\\s+yes") 117 | self.assertRegex(out, "lid-is-present:\\s+yes") 118 | self.assertRegex(out, " present:\\s+yes") 119 | self.assertRegex(out, " percentage:\\s+30%") 120 | self.assertRegex(out, " time to empty:\\s+20.0 min") 121 | self.assertRegex(out, " state:\\s+discharging") 122 | 123 | def test_charging_battery(self): 124 | path = self.dbusmock.AddChargingBattery("mock_BAT", "Mock Battery", 30.0, 1200) 125 | self.assertEqual(path, "/org/freedesktop/UPower/devices/mock_BAT") 126 | 127 | self.assertRegex( 128 | self.p_mock.stdout.read(), 129 | b'emit /org/freedesktop/UPower org.freedesktop.UPower.DeviceAdded "/org/freedesktop/UPower/devices/mock_BAT"\n', 130 | ) 131 | 132 | out = subprocess.check_output(["upower", "--dump"], text=True) 133 | self.assertRegex(out, "Device: " + path) 134 | # note, Add* is not magic: this just adds an object, not change 135 | # properties 136 | self.assertRegex(out, "on-battery:\\s+yes") 137 | self.assertRegex(out, "lid-is-present:\\s+yes") 138 | self.assertRegex(out, " present:\\s+yes") 139 | self.assertRegex(out, " percentage:\\s+30%") 140 | self.assertRegex(out, " time to full:\\s+20.0 min") 141 | self.assertRegex(out, " state:\\s+charging") 142 | 143 | def test_enumerate(self): 144 | self.dbusmock.AddAC("mock_AC", "Mock AC") 145 | self.assertEqual(self.obj_upower.EnumerateDevices(), ["/org/freedesktop/UPower/devices/mock_AC"]) 146 | 147 | def test_display_device_default(self): 148 | path = self.obj_upower.GetDisplayDevice() 149 | self.assertEqual(path, "/org/freedesktop/UPower/devices/DisplayDevice") 150 | display_dev = self.dbus_con.get_object("org.freedesktop.UPower", path) 151 | props = display_dev.GetAll("org.freedesktop.UPower.Device") 152 | 153 | # http://cgit.freedesktop.org/upower/tree/src/org.freedesktop.UPower.xml 154 | # defines the properties which are defined 155 | self.assertEqual( 156 | set(props.keys()), 157 | { 158 | "Type", 159 | "State", 160 | "Percentage", 161 | "Energy", 162 | "EnergyFull", 163 | "EnergyRate", 164 | "TimeToEmpty", 165 | "TimeToFull", 166 | "IsPresent", 167 | "IconName", 168 | "WarningLevel", 169 | }, 170 | ) 171 | 172 | # not set up by default, so should not present 173 | self.assertEqual(props["IsPresent"], False) 174 | self.assertEqual(props["IconName"], "") 175 | self.assertEqual(props["WarningLevel"], UP_DEVICE_LEVEL_NONE) 176 | 177 | def test_setup_display_device(self): 178 | self.dbusmock.SetupDisplayDevice(2, 1, 50.0, 40.0, 80.0, 2.5, 3600, 1800, True, "half-battery", 3) 179 | 180 | path = self.obj_upower.GetDisplayDevice() 181 | display_dev = self.dbus_con.get_object("org.freedesktop.UPower", path) 182 | props = display_dev.GetAll("org.freedesktop.UPower.Device") 183 | 184 | # just some spot-checks, check all the values from upower -d 185 | self.assertEqual(props["Type"], 2) 186 | self.assertEqual(props["Percentage"], 50.0) 187 | self.assertEqual(props["WarningLevel"], 3) 188 | 189 | env = os.environ.copy() 190 | env["LC_ALL"] = "C" 191 | try: 192 | del env["LANGUAGE"] 193 | except KeyError: 194 | pass 195 | 196 | out = subprocess.check_output(["upower", "--dump"], text=True, env=env) 197 | self.assertIn("/DisplayDevice\n", out) 198 | self.assertIn(" battery\n", out) # type 199 | self.assertRegex(out, r"state:\s+charging") 200 | self.assertRegex(out, r"percentage:\s+50%") 201 | self.assertRegex(out, r"energy:\s+40 Wh") 202 | self.assertRegex(out, r"energy-full:\s+80 Wh") 203 | self.assertRegex(out, r"energy-rate:\s+2.5 W") 204 | self.assertRegex(out, r"time to empty:\s+1\.0 hours") 205 | self.assertRegex(out, r"time to full:\s+30\.0 minutes") 206 | self.assertRegex(out, r"present:\s+yes") 207 | self.assertRegex(out, r"icon-name:\s+'half-battery'") 208 | self.assertRegex(out, r"warning-level:\s+low") 209 | 210 | 211 | if __name__ == "__main__": 212 | # avoid writing to stderr 213 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 214 | -------------------------------------------------------------------------------- /tests/test_urfkill.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify it under 2 | # the terms of the GNU Lesser General Public License as published by the Free 3 | # Software Foundation; either version 3 of the License, or (at your option) any 4 | # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text 5 | # of the license. 6 | 7 | __author__ = "Jussi Pakkanen" 8 | __copyright__ = """ 9 | (c) 2015 Canonical Ltd. 10 | (c) 2017 - 2022 Martin Pitt 11 | """ 12 | 13 | import fcntl 14 | import os 15 | import subprocess 16 | import sys 17 | import unittest 18 | 19 | import dbus 20 | 21 | import dbusmock 22 | 23 | 24 | def _get_urfkill_objects(): 25 | bus = dbus.SystemBus() 26 | remote_object = bus.get_object("org.freedesktop.URfkill", "/org/freedesktop/URfkill") 27 | iface = dbus.Interface(remote_object, "org.freedesktop.URfkill") 28 | return (remote_object, iface) 29 | 30 | 31 | class TestURfkill(dbusmock.DBusTestCase): 32 | """Test mocked URfkill""" 33 | 34 | @classmethod 35 | def setUpClass(cls): 36 | cls.start_system_bus() 37 | cls.dbus_con = cls.get_dbus(True) 38 | 39 | def setUp(self): 40 | (self.p_mock, self.obj_urfkill) = self.spawn_server_template("urfkill", {}, stdout=subprocess.PIPE) 41 | # set log to nonblocking 42 | flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL) 43 | fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) 44 | self.dbusmock = dbus.Interface(self.obj_urfkill, dbusmock.MOCK_IFACE) 45 | 46 | def tearDown(self): 47 | self.p_mock.stdout.close() 48 | self.p_mock.terminate() 49 | self.p_mock.wait() 50 | 51 | def test_mainobject(self): 52 | (remote_object, iface) = _get_urfkill_objects() 53 | self.assertFalse(iface.IsFlightMode()) 54 | propiface = dbus.Interface(remote_object, dbus.PROPERTIES_IFACE) 55 | version = propiface.Get("org.freedesktop.URfkill", "DaemonVersion") 56 | self.assertEqual(version, "0.6.0") 57 | 58 | def test_subobjects(self): 59 | bus = dbus.SystemBus() 60 | individual_objects = ["BLUETOOTH", "FM", "GPS", "NFC", "UWB", "WIMAX", "WLAN", "WWAN"] 61 | for i in individual_objects: 62 | path = "/org/freedesktop/URfkill/" + i 63 | remote_object = bus.get_object("org.freedesktop.URfkill", path) 64 | propiface = dbus.Interface(remote_object, dbus.PROPERTIES_IFACE) 65 | state = propiface.Get("org.freedesktop.URfkill.Killswitch", "state") 66 | self.assertEqual(state, 0) 67 | 68 | def test_block(self): 69 | bus = dbus.SystemBus() 70 | (_, iface) = _get_urfkill_objects() 71 | 72 | property_object = bus.get_object("org.freedesktop.URfkill", "/org/freedesktop/URfkill/WLAN") 73 | propiface = dbus.Interface(property_object, dbus.PROPERTIES_IFACE) 74 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0) 75 | 76 | self.assertTrue(iface.Block(1, True)) 77 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1) 78 | 79 | self.assertTrue(iface.Block(1, False)) 80 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0) 81 | 82 | # 99 is an unknown type to the mock, so it should return false. 83 | self.assertFalse(iface.Block(99, False)) 84 | 85 | def test_flightmode(self): 86 | bus = dbus.SystemBus() 87 | (_, iface) = _get_urfkill_objects() 88 | 89 | property_object = bus.get_object("org.freedesktop.URfkill", "/org/freedesktop/URfkill/WLAN") 90 | propiface = dbus.Interface(property_object, dbus.PROPERTIES_IFACE) 91 | 92 | self.assertFalse(iface.IsFlightMode()) 93 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0) 94 | iface.FlightMode(True) 95 | self.assertTrue(iface.IsFlightMode()) 96 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1) 97 | iface.FlightMode(False) 98 | self.assertFalse(iface.IsFlightMode()) 99 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0) 100 | 101 | def test_flightmode_restore(self): 102 | # An interface that was blocked remains blocked once flightmode is removed. 103 | bus = dbus.SystemBus() 104 | (_, iface) = _get_urfkill_objects() 105 | 106 | property_object = bus.get_object("org.freedesktop.URfkill", "/org/freedesktop/URfkill/WLAN") 107 | propiface = dbus.Interface(property_object, dbus.PROPERTIES_IFACE) 108 | 109 | self.assertFalse(iface.IsFlightMode()) 110 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0) 111 | iface.Block(1, True) 112 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1) 113 | iface.FlightMode(True) 114 | self.assertTrue(iface.IsFlightMode()) 115 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1) 116 | iface.FlightMode(False) 117 | self.assertFalse(iface.IsFlightMode()) 118 | self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1) 119 | 120 | 121 | if __name__ == "__main__": 122 | # avoid writing to stderr 123 | unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout)) 124 | --------------------------------------------------------------------------------