├── .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 | 
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 |
--------------------------------------------------------------------------------