././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9943736
python_dbusmock-0.37.1/ 0000755 0001751 0001751 00000000000 15071377700 014467 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9823737
python_dbusmock-0.37.1/.fmf/ 0000755 0001751 0001751 00000000000 15071377700 015315 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/.fmf/version 0000644 0001751 0001751 00000000002 15071377476 016727 0 ustar 00runner runner 1
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9803736
python_dbusmock-0.37.1/.github/ 0000755 0001751 0001751 00000000000 15071377700 016027 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9833736
python_dbusmock-0.37.1/.github/workflows/ 0000755 0001751 0001751 00000000000 15071377700 020064 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/.github/workflows/pages.yml 0000644 0001751 0001751 00000002274 15071377476 021725 0 ustar 00runner runner name: Deploy to GitHub pages
on:
push:
branches: ["main"]
# Allow running this workflow manually from the Actions tab
workflow_dispatch:
# Allow only one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v4
- name: Install dependencies from pip
run: pip install sphinx sphinx-rtd-theme sphinx-autoapi myst-parser
- name: Build with sphinx
run: sphinx-build -M html doc/ doc/
- name: Move output to target directory
run: mv doc/html public_html/
- name: Upload pages artifact
id: deployment
uses: actions/upload-pages-artifact@v3
with:
path: public_html/
deploy:
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/.github/workflows/release.yml 0000644 0001751 0001751 00000003027 15071377476 022243 0 ustar 00runner runner name: tag
on:
push:
tags:
# this is a glob, not a regexp
- '[0-9]*'
jobs:
release:
runs-on: ubuntu-24.04
environment: release
permissions:
# PyPI trusted publisher
id-token: write
# create release
contents: write
steps:
- name: Clone repository
uses: actions/checkout@v5
with:
# need this to also fetch tags
fetch-depth: 0
- name: Workaround for https://github.com/actions/checkout/pull/697
run: git fetch --force origin $(git describe --tags):refs/tags/$(git describe --tags)
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3-build python3-setuptools python3-setuptools-scm python3-venv
- name: Build release tarball
run: python3 -m build --sdist
- name: Sanity check
run: |
set -eux
mkdir tmp
cd tmp
tar xf ../dist/python_dbusmock-${{ github.ref_name }}.tar.gz
cd python_dbusmock-*
test "$(PYTHONPATH=. python3 -c 'import dbusmock; print(dbusmock.__version__)')" = "${{ github.ref_name }}"
PYTHONPATH=. python3 tests/test_api.py
cd ../..
rm -rf tmp
- name: Create GitHub release
uses: cockpit-project/action-release@7d2e2657382e8d34f88a24b5987f2b81ea165785
with:
filename: "dist/python_dbusmock-${{ github.ref_name }}.tar.gz"
- name: Create PyPy release
uses: pypa/gh-action-pypi-publish@release/v1
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/.github/workflows/tests.yml 0000644 0001751 0001751 00000002222 15071377476 021761 0 ustar 00runner runner name: unit-tests
on:
push:
pull_request:
schedule:
- cron: 0 4 * * MON,FRI
jobs:
OS:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
scenario:
- docker.io/debian:unstable
- docker.io/ubuntu:devel
- docker.io/ubuntu:rolling
- docker.io/ubuntu:latest
- registry.fedoraproject.org/fedora:latest
- registry.fedoraproject.org/fedora:rawhide
- quay.io/centos/centos:stream10-development
timeout-minutes: 30
steps:
- name: Clone repository
uses: actions/checkout@v5
with:
# need this to also fetch tags
fetch-depth: 0
- name: Workaround for https://github.com/actions/checkout/pull/697
run: |
set -ex
TAG=$(git describe --tags)
if echo "$TAG" | grep -q '^[0-9.]\+$'; then
git fetch --force origin $TAG:refs/tags/$TAG
fi
- name: Run unit tests
run: |
dpkg -s podman docker || true
cat /etc/apt/sources.list
cat /etc/apt/sources.list.d/*
${{ matrix.env }} tests/run ${{ matrix.scenario }}
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/.gitignore 0000644 0001751 0001751 00000000133 15071377476 016466 0 ustar 00runner runner *.egg-info
/build
/dbusmock/_version.py
/dist
/doc/doctrees
/doc/html
MANIFEST
__pycache__
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/.pre-commit-config.yaml 0000644 0001751 0001751 00000000732 15071377476 020764 0 ustar 00runner runner repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-merge-conflict
- id: check-toml
- id: check-symlinks
- id: no-commit-to-branch
args: ['--branch', 'main']
- repo: https://github.com/psf/black
rev: 23.11.0
hooks:
- id: black
args: ['--check', '--diff', '.']
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.1.6
hooks:
- id: ruff
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/COPYING 0000644 0001751 0001751 00000016743 15071377476 015547 0 ustar 00runner runner GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/NEWS 0000644 0001751 0001751 00000051033 15071377476 015202 0 ustar 00runner runner OBSOLETE: Release news of newer versions are contained in the git tag descriptions.
## [0.28.1] - 2022-06-28
- Again works on RHEL/CentOS 8 (0.27 broke there), now in CI
- Avoid glib GI dependency for main dbusmock, for running in virtualenv (thanks Allison Karlitskaya)
## [0.28.0] - 2022-06-19
- Drop unmaintained and broken accountsservice template
- testcase: Throw an error when spawning a well-known name that exists (thanks Benjamin Berg)
- mockobject: Allow sending signals with extra details (thanks Peter Hutterer)
## [0.27.5] - 2022-04-05
- bluez and accountsservice templates: Drop default arguments from D-Bus methods (thanks Simon McVittie)
## [0.27.4] - 2022-04-04
- Fix D-Bus signature detection regression from 0.27.0 (thanks Peter Hutterer) (#118)
## [0.27.3] - 2022-03-22
- packit: Fix file name to sync
## [0.27.2] - 2022-03-22
- packit: Really fix Fedora dist-git syncing
## [0.27.1] - 2022-03-22
- packit: Fix Fedora dist-git syncing
## [0.27.0] - 2022-03-22
- Do not register standard session service directories, add API to enable selected services (thanks Benjamin Berg)
- Log static method calls from templates (thanks Peter Hutterer)
## [0.26.1] - 2022-02-25
- Fix README content type to Markdown, to fix releasing to PyPi
## [0.26.0] - 2022-02-25
- logind template: Add locking API (thanks Andy Holmes)
- bluez5 template: Add RemoveDevice() and RemoveAdapterWithDevices() methods,
fix properties (thanks Bastien Nocera)
- Documentation improvements, particularly wrt. raising errors
## [0.25.0] - 2021-12-25
- bluez template: Implement adapter discovery, connect, disconnect, and removal
(thanks Bastien Nocera)
- Fix changing array properties (thanks Jonas Ådahl)
- Fix CLI upower tests (thanks Marco Trevisan)
- Add testing and Fedora updating through packit
## [0.24.1] - 2021-10-27
- Do not register standard D-Bus service directories (Thanks Benjamin Berg)
- templates: Add IIO Sensors Proxy support (Thanks Marco Trevisan)
- Fix importlib module import (Thanks Marco Trevisan)
- Clean up code for most recent pylint version
## [0.24.0] - 2021-08-28
- Add Add power-profiles-daemon template (Thanks Bastien Nocera)
- logind: Implement Inhibit and ListInhibitors (Thanks to Benjamin Berg)
- Fix new complaints from pylint 2.9
## [0.23.1] - 2021-06-15
- Fix tests for Python 3.10
## [0.23.0] - 2021-03-24
- DBusMockObject: Add UpdateProperties() method (Thanks to Jonas Ådahl)
- DBusTestCase: Add bus override argument to spawn_server_template, to use
templates on either system or session bus; add corresponding --session CLI
option
- bluez template: Implement Pair() method on the Device interface and the
AgentManager1 interface (Thanks to Bastien Nocera)
- polkit template: Implement RegisterAuthenticationAgent() (Thanks to Jonas Ådahl)
- Add accountsservice template (Thanks to Marco Trevisan)
## [0.22.0] - 2021-01-02
- NetworkManager template: Fix connecting to wifi
- NetworkManager template: Add Reload() method
- tests: Replace nose test runner with standard unittest
- setup.py: Drop deprecated `test_suite`, run tests through
`python3 -m unittest` directly
## [0.21.0] - 2021-01-01
- Add type annotations to the whole API
- Drop obsolete ConsoleKit and bluez4 templates/tests
- upower template: Drop support for 0.9 API
## [0.20.0] - 2020-12-19
- NetworkManager template: Handle NetworkingEnable
- NetworkManager template: AddConnectionUnsaved
- Drop support for Python 2
## [0.19] - 2020-01-09
- NetworkManager template: Add "StateReason" property and active connection ID
- Add low-memory-monitor template
## [0.18.3] - 2019-09-08
- Fix timeouts for loaded machines and parallel tests
- Fix tests for Python 3.8
## [0.18.2] - 2019-02-23
- test: Adjust for changed version output format in systemd 241
## [0.18.1] - 2018-11-11
- test: Fix failures with systemd 239 in chroots
## [0.18] - 2018-07-01
- logind template: Add support for suspend-then-hibernate
- NetworkManager template: Add GetConnectionByUuid method
- bluez template: Add PairDevice() 'class_' parameter
- Fix logging of unicode strings in Python 2
- Adjust logind and timedated tests to systemd 239 (Debian #902602)
## [0.17.2] - 2018-03-01
- NetworkManager template: Add GetDeviceByIpIface().
- Move release tarballs from Launchpad to
.
remains as usual.
## [0.17.1] - 2018-02-22
- Fix NetworkManager tests due to name changes from 802-11-wireless to wifi.
## [0.17] - 2017-11-07
- Call dbus-daemon directly instead of dbus-launch. (Debian #836053)
- Fix DBusTestCase.stop_dbus() to actually enforce a timeout. Thanks to Renat
Galimov!
- Fix test_timedated for changed timedatectl output format in systemd 235.
## [0.16.9] - 2017-06-19
- NetworkManager template: Fix type of 'State' property in AddActiveConnection()
- Fix BlueZ tests to work with Python 2 again.
## [0.16.8] - 2017-06-12
- bluez5 template tests: Fix failure of test_no_adapters with BlueZ 5.45.
(LP: #1696480)
- tests: Move from pep8 to pycodestyle for static code tests.
- NetworkManager: Add ObjectManager, to work with NM ≥ 1.6.
## [0.16.7] - 2016-09-12
- bluez5 template tests: Fix failure on "Waiting to connect to bluetoothd"
messages.
## [0.16.6] - 2016-06-19
- upower template tests: Fix version comparison.
## [0.16.5] - 2016-06-19
- Ofono template: generate unique ICCIDs (CardIdentifier).
## [0.16.4] - 2016-06-07
- Ofono template: Add missing properties to SimManager interface.
## [0.16.3] - 2015-12-14
- NetworkManager template test: Make the test run standalone. Thanks Pete
Woods.
## [0.16.2] - 2015-12-09
- NetworkManager template: Fix connection settings Updated signal emitted by
wrong object.
- NetworkManager template: Handle empty device at connection activation.
- NetworkManager template: Implement secrets management in settings.
## [0.16.1] - 2015-10-22
- NetworkManager template: Fix indexing bug in SettingsAddConnection. Thanks
Pete Woods.
## [0.16] - 2015-10-08
- NetworkManager template: Generate a new unused name in connection activation
instead of just using the access point name. Thanks Pete Woods for the
original patch!
- Allow the passing of template parameters via the command-line as JSON
strings. Thanks Pete Woods.
## [0.15.3] - 2015-09-16
- NetworkManager template: Add missing properties to ethernet device and
active connection. Thanks Pete Woods.
- Quiesce irrelevant PEP-8 errors with pep8 1.6.
## [0.15.2] - 2015-06-11
- test_ofono: Test fields which don't get obfuscated with Ubuntu's latest
ofono (See LP #1459983). Thanks Iain Lane.
- timedated template: Add NTPSynchronized property and set it in SetNTP(), to
also work with systemd 220.
## [0.15.1] - 2015-05-12
- SECURITY FIX: When loading a template from an arbitrary file through the
AddTemplate() D-Bus method call or DBusTestCase.spawn_server_template()
Python method, don't create or use Python's *.pyc cached files. By tricking
a user into loading a template from a world-writable directory like /tmp, an
attacker could run arbitrary code with the user's privileges by putting a
crafted .pyc file into that directory. Note that this is highly unlikely to
actually appear in practice as custom dbusmock templates are usually shipped
in project directories, not directly in world-writable directories.
Thanks to Simon McVittie for discovering this!
(LP: #1453815, CVE-2015-1326)
## [0.15] - 2015-05-08
- NetworkManager template: Restore nm-specific PropertiesChanged signal
- NetworkManager template: Add DeactivateConnection(),
Settings.AddConnection(), Settings.Connection.Update(), and
Settings.Connection.Delete() methods. Also allow Connections with
autoconnect, added using AddConnection, to be automatically connected by the
first found device, roughly like NetworkManager itself does. Thanks Jonas
Grønås Drange!
- NetworkManager template: Fix broken exception in AddWiFiConnection.
- NetworkManager template: Set RsnFlags to have the same value as WpaFlags.
Thanks Pete Woods!
## [0.14] - 2015-03-30
- Move project hosting to github, update README.rst.
- urfkill template: Return boolean from block() method, as the original
urfkill does. Thanks Jonas Grønås Drange!
- Correctly instantiate DBusExceptions so that they get a proper name and
message (issue #3)
- ofono template: Fix SubscriberIdentity property type
- Emit PropertiesChanged signal when Set()ing properties.
- urfkill template: Return boolean from Block() and FlightMode() methods
- ofono template: Add ConnectionManager interface.
- NetworkManager template: Much more complete support for mocking access
points and connections.
## [0.13] - 2015-02-27
- Add urfkill template. Thanks Jussi Pakkanen!
## [0.12] - 2015-01-17
- upower template: Add SetDeviceProperties() convenience method for changing
upower device properties. Thanks Charles Kerr!
## [0.11.4] - 2014-09-22
- upower template: Go back to using type 's' for Device* signal arguments for
upower 0.9. The older library does not get along with 'o'. (Regression in
0.11.2)
## [0.11.3] - 2014-09-19
- Fix test suite crash if loginctl is not installed.
## [0.11.2] - 2014-09-19
- upower template: Fix type of Device* signal arguments to be 'o', not 's'.
Thanks Iain Lane.
- ofono template: Add org.ofono.SimManager interface. Thanks Jonas Grønås
Drange.
- bluez5 template: Fix the type of the 'Transferred' property. Thanks Philip
Withnall.
- networkmanager template: Fix the "FAILED" state value to be 120, not 12.
Thanks Jonas Grønås Drange.
- bluez5 tests: Accept devices in state "not available" (or other states) as
well.
- timedated and logind tests: Adjust to also accept output format of
systemd 215.
## [0.11.1] - 2014-08-08
- Rename bluez test classes to TestBlueZ4 and TestBlueZ5, to tell them apart
in the output.
- logind template: Fix type of IdleSinceHint property, and add
IdleSinceHintMonotonic and IdleActionUSec properties. (LP: #1348437)
- bluez4 template: Fix settings properties in StartDiscovery/StopDiscovery.
Thanks to Mathieu Trudel-Lapierre.
- NetworkManager template: Add "Devices" and "AccessPoints" properties of
NetworkManager 0.9.10. (LP: #1328579)
- NetworkManager template: Fix the types of the AccessPoint properties, and
add some more properties.
- Adjust NetworkManager template tests for changed strings in NM 0.9.10.
## [0.11] - 2014-07-24
- ofono: Fix GetOperators() to only apply to its own modem, not all modems.
Thanks to Jonas Grønås Drange.
- Add template for BlueZ 4. Thanks to Mathieu Trudel-Lapierre!
## [0.10.3] - 2014-07-16
- Fix upower tests to work for upower 0.99 in environments without a system
D-Bus.
## [0.10.2] - 2014-07-16
- ofono: Make Scan() do the same as GetOperators(). Thanks Iain Lane.
- README.rst: Clarify that the "True" argument to get_dbus() means the system
bus.
- Fix code to be compliant with pep8 1.5.
- Fix TestCLI.test_template_system test with upower 0.99. (LP: #1330037)
- ofono template: Support adding a second modem with AddModem().
(LP: #1340590)
## [0.10.1] - 2014-01-30
- Move code from bzr to git, adjust README.rst, do-release, and other files
accordingly.
- timedated template: Emit PropertiesChanged when setting properties. Thanks
Iain Lane.
## [0.10] - 2013-12-18
- Add dynamic properties to introspection XML, to make mocked properties work
with Qt's property support, d-feet, and other tools. Thanks Iain Lane!
- Fix frequent KeyError() when calling static methods from a template.
Regression from 0.9.
- Support having the same method name on different D-Bus interfaces.
- Add ofono template with support for the Manager, VoiceCallManager, and
NetworkRegistration/NetworkOperator interfaces.
- Add template for systemd's timedated. Thanks Iain Lane!
## [0.9.2] - 2013-12-13
- upower template: Emit DeviceAdded when adding new a new AC adapter or
battery. Thanks Iain Lane!
- Fix ResourceWarnings in test suite.
## [0.9.1] - 2013-12-10
- Fix UnicodeDecodeError in NEWS parsing when trying to build with a C locale.
Thanks Dmitry Shachnev.
## [0.9] - 2013-11-29
- Make static template methods appear in introspection. Thanks Philip
Whitnall! (LP: #1250096)
- Add support for the D-Bus ObjectManager interface. This can now be enabled
in templates (IS_OBJECT_MANAGER = True) or the CLI (--is-object-manager/-m).
Thanks Philip Whitnall!
- Add Reset() mock interface method to reset that object’s state (including
removing all its sub-objects) as if the object or template had been
destroyed and re-created. This is intended for use at the end of test cases
to reset state before the next test case is run, as an alternative to
killing python-dbusmock and re-starting it. Thanks Philip Whitnall!
- logind template: Fix return value of CanSuspend() and related methods,
calling them previously caused a crash.
- Don't close the log file multiple times if it is shared between objects.
Thanks Philip Whitnall!
- Log calls to Get(), GetAll(), and Set().
- Add templates for BlueZ 5 and BlueZ-OBEX. These templates are moderately
well featured, though currently targeted exclusively at testing OBEX vCard
transfers from phones to computers. Thanks Philip Whitnall!
## [0.8.1] - 2013-11-11
- Fix test failure if the "upower" tool is not installed.
- Fix bad upower test which assumed a locale with a comma decimal separator.
## [0.8] - 2013-11-08
- upower template: Change default daemon version to "0.9", and fix tests to
work with both upower 0.9 and 1.0 client tool.
- upower template: Provide 1.0 API if DaemonVersion property/template
parameter gets set to >= "0.99". (LP: #1245955)
- upower template: Add SetupDisplayDevice() method on the mock interface to
conveniently configure all defined properties of the DisplayDevice. Only
available when using the 1.0 API. (LP: #1245955)
## [0.7.2] - 2013-08-30
- Add optional "timeout" argument to DBusTestCase.wait_for_bus_object().
(LP: #1218318)
- DBusTestCase.start_system_bus(): Make the fake bus look more like a real
system bus by specifying a configuration file with type "system".
## [0.7.1] - 2013-08-02
- Handle the "Disconnected" signal only if it comes from the D-Bus object, to
avoid accidentally reacting to other signals (like from BlueZ). Thanks
Lucas De Marchi.
## [0.7] - 2013-07-30
- Accept *.py files for the--template command line option, similar to
AddTemplate(). Thanks Emanuele Aina.
- Pass log file to objects created with AddObject(). Thanks Emanuele Aina.
- NetworkManager template: Add new method AddWiFiConnection(), to simulate a
connection associated to a previously added device (with AddWiFiDevice()).
Thanks Pete Woods.
## [0.6.3] - 2013-06-13
- Drop "can-suspend" and "can-hibernate" checks from upower template test.
upower 1.0 deprecates this functionality, and defaults to not building it.
## [0.6.2] - 2013-06-13
- Fix test_api.TestSubclass test to work without an existing session D-Bus.
## [0.6.1] - 2013-06-13
- Add dbusmock.__version__ attribute, as per PEP-0396.
- Fix not being able to inherit from DBusMockObject. Thanks Lucas De Marchi!
- Allow subclassed DBusMockObject constructors to specify props arguments as
None.
- Fix failing notification-daemon tests for too old libnotify. (LP: #1190208)
- notification_daemon template: Generate new IDs for Notify() calls with an
input ID of 0. (LP: #1190209 part 1)
- notification_daemon template: Send NotificationClosed signal in
CloseNotification() method for valid IDs. (LP: #1190209 part 2)
## [0.6] - 2013-03-20
- Emit MethodCalled() signal on the DBusMock interface, as an alternative way
of verifying method calls. Thanks Lars Uebernickel.
- DBusMockObject.AddTemplate() and DBusTestCase.spawn_server_template() can
now load local templates by specifying a path to a *.py file as template
name. Thanks Lucas De Marchi.
- Quit mock process if the D-Bus it connected to goes down. (LP: #1156561)
- Support Get() and GetAll() with empty interface names, default to main
interface in this case.
- Add template for systemd's logind.
## [0.5] - 2013-02-03
- upower template: Change LidIsClosed property default value to False.
- Add polkitd template. (LP: #1112551)
## [0.4.0] - 2013-01-21
- Add GetCalls(), GetMethodCalls() and ClearCalls() methods on the mock D-Bus
interface to query the call log, for cases where parsing stdout is
inconvenient. Thanks to Robert Bruce Park for the patch! (LP: #1099483)
- Document how to keep internal state in AddMethod().
- Add template for gnome-screensaver. Thanks to Bastien Nocera!
- Fix logging of signal arguments to have the same format as method call
arguments.
## [0.3.1] - 2013-01-07
- upower template: Set Energy and EnergyFull properties.
## [0.3] - 2012-12-19
- In the logging of mock method calls, log the arguments as well.
- Fix race condition in waiting for mock to get online on the bus, by avoiding
triggering D-Bus service activation with system-installed services.
- Add "notification_daemon" mock template.
## [0.2.2] - 2012-11-27
- tests: Suppress "nmcli and NetworkManager versions don't match" warning.
- networkmanager template: Add DeviceState enum for easier specification of
states. Thanks Alberto Ruiz.
- Fix deprecation warnings with PyGObject 3.7.x.
## [0.2.1] - 2012-11-15
- Fix tests to skip instead of fail if NetworkManager or upower are not
installed.
## [0.2.0] - 2012-11-15
- Turn dbusmock from a module into a package. This is transparent for API
users, but necessary for adding future subpackages and also makes the code
more maintainable.
- Run pyflakes and pep8 during test suite, if available.
- Add support for templates: You can now call AddTemplate() on the
org.freedesktop.DBus.Mock interface to load a template into the mock, or in
Python, start a server process with loading a template with
DBusTestCase.spawn_server_template(). Templates set up the common structure
of these services (their main objects, properties, and methods) so that you
do not need to carry around this common code, and only need to set up the
particular properties and specific D-Bus objects that you need. These
templates can be parameterized for common customizations, and they can
provide additional convenience methods on the org.freedesktop.DBus.Mock
interface to provide more abstract functionality like "add a battery".
- Add a first simple "upower" template with convenience methods to add AC and
battery objects, and change tests/test_upower.py to use it.
- Add a first "networkmanager" template with convenience methods to add
Ethernet/WiFi devices and access points. Thanks Iftikhar Ahmad!
- Add symbol dbusmock.MOCK_IFACE for 'org.freedesktop.DBus.Mock'.
- Add test cases for command line invocation (python3-m dbusmock ...).
## [0.1.3] - 2012-11-03
- Ship NEWS in release tarballs.
## [0.1.2] - 2012-10-10
- dbusmock.py, EmitSignal(): Convert arguments to the right data types
according to the signature.
- dbusmock.py, method calls: Convert arguments to the right data types
according to the signature.
- tests/test_api.py: Check proper handling of signature vs. argument list
length and/or type mismatch. (LP: #1064836)
- tests/test_upower.py: Add test for handling "DeviceChanged" signal.
- setup.py: Get version from NEWS file.
## [0.1.1] - 2012-10-04
- setup.py: Use README.rst as long description.
- setup.py: Add download URL.
- tests/test_consolekit.py: Skip test if ck-list-sessions is not installed.
- setup.py: Import "multiprocessing" to work around "'NoneType' object
is not callable" error when running "setup.py test" with python 2.7.
## [0.1] - 2012-10-04
- tests/test_api.py: Add tests for adding existing objects.
- dbusmock.py: Also put first (main) object into the global objects map.
- dbusmock.py: Document global objects map in AddMethod().
- tests/test_upower.py: Silence "No ... property" warnings; the test does not
create them all.
- dbusmock.py: Fix stopping of local D-Bus daemons for test subclasses.
- dbusmock.py, stop_dbus(): Wait for local D-Bus to be killed, and avoid
getting SIGTERMed ourselves.
- dbusmock.py: Add AddProperties() method to conveniently add several
properties to one interface at once. Update upower test case to use this.
- dbusmock.py: Add EmitSignal() method to emit an arbitrary signal on any
interface.
- Make code compatible with Python 2.7 as well. (LP: #1060278)
## [0.0.3] - 2012-10-01
- tests/test_api.py: Add tests for GetAll()
- wait_for_bus_object(): Poll for Introspect() instead of GetAll(), as some
services such as gnome-session don't implement GetAll() properly.
- Rename "dbus_mock" module to "dbusmock", to be more consistent with the
project name and Python module name conventions.
- Support adding properties to different interfaces.
- Support adding methods to different interfaces.
## [0.0.2] - 2012-10-01
- setup.py: Add categories.
## [0.0.1] - 2012-09-28
Initial release.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9933736
python_dbusmock-0.37.1/PKG-INFO 0000644 0001751 0001751 00000062676 15071377700 015605 0 ustar 00runner runner Metadata-Version: 2.4
Name: python-dbusmock
Version: 0.37.1
Summary: Mock D-Bus objects
Author-email: Martin Pitt
License: GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
Project-URL: homepage, https://github.com/martinpitt/python-dbusmock
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Development Status :: 6 - Mature
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: POSIX :: BSD
Classifier: Operating System :: Unix
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Software Development :: Testing :: Mocking
Classifier: Topic :: Software Development :: Testing :: Unit
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: COPYING
Requires-Dist: dbus-python
Dynamic: license-file

python-dbusmock
===============
## Purpose
With this program/Python library you can easily create mock objects on
D-Bus. This is useful for writing tests for software which talks to
D-Bus services such as upower, systemd, logind, gnome-session or others,
and it is hard (or impossible without root privileges) to set the state
of the real services to what you expect in your tests.
Suppose you want to write tests for gnome-settings-daemon's power
plugin, or another program that talks to upower. You want to verify that
after the configured idle time the program suspends the machine. So your
program calls `org.freedesktop.UPower.Suspend()` on the system D-Bus.
Now, your test suite should not really talk to the actual system D-Bus
and the real upower; a `make check` that suspends your machine will not
be considered very friendly by most people, and if you want to run this
in continuous integration test servers or package build environments,
chances are that your process does not have the privilege to suspend, or
there is no system bus or upower to begin with. Likewise, there is no
way for an user process to forcefully set the system/seat idle flag in
logind, so your tests cannot set up the expected test environment on the
real daemon.
That's where mock objects come into play: They look like the real API
(or at least the parts that you actually need), but they do not actually
do anything (or only some action that you specify yourself). You can
configure their state, behaviour and responses as you like in your test,
without making any assumptions about the real system status.
When using a local system/session bus, you can do unit or integration
testing without needing root privileges or disturbing a running system.
The Python API offers some convenience functions like
`start_session_bus()` and `start_system_bus()` for this, in a
`DBusTestCase` class (subclass of the standard `unittest.TestCase`) or
alternatively as a `@pytest.fixture`.
You can use this with any programming language, as you can run the
mocker as a normal program. The actual setup of the mock (adding
objects, methods, properties, and signals) all happen via D-Bus methods
on the `org.freedesktop.DBus.Mock` interface. You just don't have the
convenience D-Bus launch API that way.
## Simple example using Python's unittest
Picking up the above example about mocking upower's `Suspend()` method,
this is how you would set up a mock upower in your test case:
```python
import subprocess
import dbus
import dbusmock
class TestMyProgram(dbusmock.DBusTestCase):
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(system_bus=True)
def setUp(self):
self.p_mock = self.spawn_server('org.freedesktop.UPower',
'/org/freedesktop/UPower',
'org.freedesktop.UPower',
system_bus=True,
stdout=subprocess.PIPE)
# Get a proxy for the UPower object's Mock interface
self.dbus_upower_mock = dbus.Interface(self.dbus_con.get_object(
'org.freedesktop.UPower', '/org/freedesktop/UPower'),
dbusmock.MOCK_IFACE)
self.dbus_upower_mock.AddMethod('', 'Suspend', '', '', '')
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_suspend_on_idle(self):
# run your program in a way that should trigger one suspend call
# now check the log that we got one Suspend() call
self.assertRegex(self.p_mock.stdout.readline(), b'^[0-9.]+ Suspend$')
```
Let's walk through:
- We derive our tests from `dbusmock.DBusTestCase` instead of
`unittest.TestCase` directly, to make use of the convenience API
to start a local system bus.
- `setUpClass()` starts a local system bus, and makes a connection
to it available to all methods as `dbus_con`. `True` means that we
connect to the system bus, not the session bus. We can use the
same bus for all tests, so doing this once in `setUpClass()`
instead of `setUp()` is enough.
- `setUp()` spawns the mock D-Bus server process for an initial
`/org/freedesktop/UPower` object with an `org.freedesktop.UPower`
D-Bus interface on the system bus. We capture its stdout to be
able to verify that methods were called.
We then call `org.freedesktop.DBus.Mock.AddMethod()` to add a
`Suspend()` method to our new object to the default D-Bus
interface. This will not do anything (except log its call to
stdout). It takes no input arguments, returns nothing, and does
not run any custom code.
- `tearDown()` stops our mock D-Bus server again. We do this so that
each test case has a fresh and clean upower instance, but of
course you can also set up everything in `setUpClass()` if tests
do not interfere with each other on setting up the mock.
- `test_suspend_on_idle()` is the actual test case. It needs to run
your program in a way that should trigger one suspend call. Your
program will try to call `Suspend()`, but as that's now being
served by our mock instead of upower, there will not be any actual
machine suspend. Our mock process will log the method call
together with a time stamp; you can use the latter for doing
timing related tests, but we just ignore it here.
## Simple example using pytest
The same functionality as above but instead using the pytest fixture provided
by this package.
```python
import subprocess
import dbus
import pytest
import dbusmock
@pytest.fixture
def upower_mock(dbusmock_system):
p_mock = dbusmock_system.spawn_server(
'org.freedesktop.UPower',
'/org/freedesktop/UPower',
'org.freedesktop.UPower',
system_bus=True,
stdout=subprocess.PIPE)
# Get a proxy for the UPower object's Mock interface
dbus_upower_mock = dbus.Interface(dbusmock_system.get_dbus(True).get_object(
'org.freedesktop.UPower',
'/org/freedesktop/UPower'
), dbusmock.MOCK_IFACE)
dbus_upower_mock.AddMethod('', 'Suspend', '', '', '')
yield p_mock
p_mock.stdout.close()
p_mock.terminate()
p_mock.wait()
def test_suspend_on_idle(upower_mock):
# run your program in a way that should trigger one suspend call
# now check the log that we got one Suspend() call
assert upower_mock.stdout.readline() == b'^[0-9.]+ Suspend$'
```
Let's walk through:
- We import the `dbusmock_system` fixture from dbusmock which provides us
with a system bus started for our test case wherever the
`dbusmock_system` argument is used by a test case and/or a pytest
fixture.
- The `upower_mock` fixture spawns the mock D-Bus server process for an initial
`/org/freedesktop/UPower` object with an `org.freedesktop.UPower`
D-Bus interface on the system bus. We capture its stdout to be
able to verify that methods were called.
We then call `org.freedesktop.DBus.Mock.AddMethod()` to add a
`Suspend()` method to our new object to the default D-Bus
interface. This will not do anything (except log its call to
stdout). It takes no input arguments, returns nothing, and does
not run any custom code.
This mock server process is yielded to the test function that uses
the `upower_mock` fixture - once the test is complete the process is
terminated again.
- `test_suspend_on_idle()` is the actual test case. It needs to run
your program in a way that should trigger one suspend call. Your
program will try to call `Suspend()`, but as that's now being
served by our mock instead of upower, there will not be any actual
machine suspend. Our mock process will log the method call
together with a time stamp; you can use the latter for doing
timing related tests, but we just ignore it here.
## Simple example from shell
We use the actual session bus for this example. You can use
`dbus-run-session` to start a private one as well if you want, but that
is not part of the actual mocking.
So let's start a mock at the D-Bus name `com.example.Foo` with an
initial "main" object on path /, with the main D-Bus interface
`com.example.Foo.Manager`:
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager
On another terminal, let's first see what it does:
gdbus introspect --session -d com.example.Foo -o /
You'll see that it supports the standard D-Bus `Introspectable` and
`Properties` interfaces, as well as the `org.freedesktop.DBus.Mock`
interface for controlling the mock, but no "real" functionality yet.
So let's add a method:
gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod '' Ping '' '' ''
Now you can see the new method in `introspect`, and call it:
gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Ping
The mock process in the other terminal will log the method call with a
time stamp, and you'll see something like `1348832614.970 Ping`.
Now add another method with two int arguments and a return value and
call it:
gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod \
'' Add 'ii' 'i' 'ret = args[0] + args[1]'
gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Add 2 3
This will print `(5,)` as expected (remember that the return value is
always a tuple), and again the mock process will log the Add method
call.
You can do the same operations in e. g. d-feet or any other D-Bus
language binding.
## Interactive debugging
It's possible to use dbus-mock to run interactive sessions using something like:
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager -e $SHELL
Where a shell session with the defined mocks is set and others can be added.
Or more complex ones such as:
python3 -m dbusmock --session -t upower -e \
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager -e \
gdbus introspect --session -d com.example.Foo -o /
## Logging
Usually you want to verify which methods have been called on the mock
with which arguments. There are three ways to do that:
- By default, the mock process writes the call log to stdout.
- You can call the mock process with the `-l`/`--logfile` argument,
or specify a log file object in the `spawn_server()` method if you
are using Python.
- You can use the `GetCalls()`, `GetMethodCalls()` and
`ClearCalls()` methods on the `org.freedesktop.DBus.Mock` D-Bus
interface to get an array of tuples describing the calls.
## Templates
Some D-Bus services are commonly used in test suites, such as UPower or
NetworkManager. python-dbusmock provides "templates" which set up the
common structure of these services (their main objects, properties, and
methods) so that you do not need to carry around this common code, and
only need to set up the particular properties and specific D-Bus objects
that you need. These templates can be parameterized for common
customizations, and they can provide additional convenience methods on
the `org.freedesktop.DBus.Mock` interface to provide more abstract
functionality like "add a battery".
For example, for starting a server with the `upower` template in
Python you can run
(self.p_mock, self.obj_upower) = self.spawn_server_template(
'upower', {'OnBattery': True}, stdout=subprocess.PIPE)
or load a template into an already running server with the
`AddTemplate()` method; this is particularly useful if you are not using
Python:
python3 -m dbusmock --system org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower
gdbus call --system -d org.freedesktop.UPower -o /org/freedesktop/UPower -m org.freedesktop.DBus.Mock.AddTemplate 'upower' '{"OnBattery": }'
This creates all expected properties such as `DaemonVersion`, and
changes the default for one of them (`OnBattery`) through the (optional)
parameters dict.
If you do not need to specify parameters, you can do this in a simpler
way with
python3 -m dbusmock --template upower
The template does not create any devices by default. You can add some
with the template's convenience methods like
ac_path = self.dbusmock.AddAC('mock_AC', 'Mock AC')
bt_path = self.dbusmock.AddChargingBattery('mock_BAT', 'Mock Battery', 30.0, 1200)
or calling `AddObject()` yourself with the desired properties, of
course.
Templates commonly implement some non-trivial functionality with actual Python
methods and the standard [dbus-python](https://dbus.freedesktop.org/doc/dbus-python/)
[`@dbus.service.method`](https://dbus.freedesktop.org/doc/dbus-python/dbus.service.html#dbus.service.method)
decorator.
To build your own template, you can copy
[dbusmock/templates/SKELETON](./dbusmock/templates/SKELETON) to your
new template file name and replace `CHANGEME` with the actual code/values.
Look at [dbusmock/templates/upower.py](./dbusmock/templates/upower.py) for
a real-life implementation.
A template can be loaded from these locations:
* Provide a path to its `.py` file. This is intended for running tests out of
git/build trees with very project specific or unstable templates.
* From [`$XDG_DATA_DIRS/python-dbusmock/templates/`*name*`.py`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
This is intended for shipping reusable templates in distribution development
packages. Load them by module name.
* python-dbusmock [ships a set of widely applicable templates](./dbusmock/templates/)
which are collaboratively maintained, like the `upower` one in the example
above. Load them by module name.
## More Examples
Have a look at the test suite for two real-live use cases:
- `tests/test_upower.py` simulates upowerd, in a more complete way
than in above example and using the `upower` template. It verifies
that `upower --dump` is convinced that it's talking to upower.
- `tests/test_api.py` runs a mock on the session bus and exercises
all available functionality, such as adding additional objects,
properties, multiple methods, input arguments, return values, code
in methods, sending signals, raising exceptions, and introspection.
## Documentation
The `dbusmock` module has extensive documentation built in, which you
can read with e. g. `pydoc3 dbusmock` or online at
https://martinpitt.github.io/python-dbusmock/
`pydoc3 dbusmock.DBusMockObject` shows the D-Bus API of the mock object,
i. e. methods like `AddObject()`, `AddMethod()` etc. which are used to
set up your mock object.
`pydoc3 dbusmock.DBusTestCase` shows the convenience Python API for
writing test cases with local private session/system buses and launching
the server.
`pydoc3 dbusmock.templates` shows all available templates.
`pydoc3 dbusmock.templates.NAME` shows the documentation and available
parameters for the `NAME` template.
`python3 -m dbusmock --help` shows the arguments and options for running
the mock server as a program.
## Development
python-dbusmock is hosted on https://github.com/martinpitt/python-dbusmock
Run the unit tests with `python3 -m unittest` or `pytest`.
In CI, the unit tests run in containers. You can run them locally with e.g.
tests/run registry.fedoraproject.org/fedora:latest
Check the [unit-tests GitHub workflow](.github/workflows/tests.yml) for the
operating systems/container images on which python-dbusmock is tested and
supported.
To debug failures interactively, run
DEBUG=1 tests/run [image]
which will sleep on failures. You can then attach to the running container
image with e.g. `podman exec -itl bash`. The `/source` directory is mounted from the
host, i.e. edit files in your normal git checkout outside of the container, and
re-run all tests in the container shell like above. You can also run a specific
test:
python3 -m unittest tests.test_api.TestAPI.test_onearg_ret
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/README.md 0000644 0001751 0001751 00000037341 15071377476 015770 0 ustar 00runner runner 
python-dbusmock
===============
## Purpose
With this program/Python library you can easily create mock objects on
D-Bus. This is useful for writing tests for software which talks to
D-Bus services such as upower, systemd, logind, gnome-session or others,
and it is hard (or impossible without root privileges) to set the state
of the real services to what you expect in your tests.
Suppose you want to write tests for gnome-settings-daemon's power
plugin, or another program that talks to upower. You want to verify that
after the configured idle time the program suspends the machine. So your
program calls `org.freedesktop.UPower.Suspend()` on the system D-Bus.
Now, your test suite should not really talk to the actual system D-Bus
and the real upower; a `make check` that suspends your machine will not
be considered very friendly by most people, and if you want to run this
in continuous integration test servers or package build environments,
chances are that your process does not have the privilege to suspend, or
there is no system bus or upower to begin with. Likewise, there is no
way for an user process to forcefully set the system/seat idle flag in
logind, so your tests cannot set up the expected test environment on the
real daemon.
That's where mock objects come into play: They look like the real API
(or at least the parts that you actually need), but they do not actually
do anything (or only some action that you specify yourself). You can
configure their state, behaviour and responses as you like in your test,
without making any assumptions about the real system status.
When using a local system/session bus, you can do unit or integration
testing without needing root privileges or disturbing a running system.
The Python API offers some convenience functions like
`start_session_bus()` and `start_system_bus()` for this, in a
`DBusTestCase` class (subclass of the standard `unittest.TestCase`) or
alternatively as a `@pytest.fixture`.
You can use this with any programming language, as you can run the
mocker as a normal program. The actual setup of the mock (adding
objects, methods, properties, and signals) all happen via D-Bus methods
on the `org.freedesktop.DBus.Mock` interface. You just don't have the
convenience D-Bus launch API that way.
## Simple example using Python's unittest
Picking up the above example about mocking upower's `Suspend()` method,
this is how you would set up a mock upower in your test case:
```python
import subprocess
import dbus
import dbusmock
class TestMyProgram(dbusmock.DBusTestCase):
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(system_bus=True)
def setUp(self):
self.p_mock = self.spawn_server('org.freedesktop.UPower',
'/org/freedesktop/UPower',
'org.freedesktop.UPower',
system_bus=True,
stdout=subprocess.PIPE)
# Get a proxy for the UPower object's Mock interface
self.dbus_upower_mock = dbus.Interface(self.dbus_con.get_object(
'org.freedesktop.UPower', '/org/freedesktop/UPower'),
dbusmock.MOCK_IFACE)
self.dbus_upower_mock.AddMethod('', 'Suspend', '', '', '')
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_suspend_on_idle(self):
# run your program in a way that should trigger one suspend call
# now check the log that we got one Suspend() call
self.assertRegex(self.p_mock.stdout.readline(), b'^[0-9.]+ Suspend$')
```
Let's walk through:
- We derive our tests from `dbusmock.DBusTestCase` instead of
`unittest.TestCase` directly, to make use of the convenience API
to start a local system bus.
- `setUpClass()` starts a local system bus, and makes a connection
to it available to all methods as `dbus_con`. `True` means that we
connect to the system bus, not the session bus. We can use the
same bus for all tests, so doing this once in `setUpClass()`
instead of `setUp()` is enough.
- `setUp()` spawns the mock D-Bus server process for an initial
`/org/freedesktop/UPower` object with an `org.freedesktop.UPower`
D-Bus interface on the system bus. We capture its stdout to be
able to verify that methods were called.
We then call `org.freedesktop.DBus.Mock.AddMethod()` to add a
`Suspend()` method to our new object to the default D-Bus
interface. This will not do anything (except log its call to
stdout). It takes no input arguments, returns nothing, and does
not run any custom code.
- `tearDown()` stops our mock D-Bus server again. We do this so that
each test case has a fresh and clean upower instance, but of
course you can also set up everything in `setUpClass()` if tests
do not interfere with each other on setting up the mock.
- `test_suspend_on_idle()` is the actual test case. It needs to run
your program in a way that should trigger one suspend call. Your
program will try to call `Suspend()`, but as that's now being
served by our mock instead of upower, there will not be any actual
machine suspend. Our mock process will log the method call
together with a time stamp; you can use the latter for doing
timing related tests, but we just ignore it here.
## Simple example using pytest
The same functionality as above but instead using the pytest fixture provided
by this package.
```python
import subprocess
import dbus
import pytest
import dbusmock
@pytest.fixture
def upower_mock(dbusmock_system):
p_mock = dbusmock_system.spawn_server(
'org.freedesktop.UPower',
'/org/freedesktop/UPower',
'org.freedesktop.UPower',
system_bus=True,
stdout=subprocess.PIPE)
# Get a proxy for the UPower object's Mock interface
dbus_upower_mock = dbus.Interface(dbusmock_system.get_dbus(True).get_object(
'org.freedesktop.UPower',
'/org/freedesktop/UPower'
), dbusmock.MOCK_IFACE)
dbus_upower_mock.AddMethod('', 'Suspend', '', '', '')
yield p_mock
p_mock.stdout.close()
p_mock.terminate()
p_mock.wait()
def test_suspend_on_idle(upower_mock):
# run your program in a way that should trigger one suspend call
# now check the log that we got one Suspend() call
assert upower_mock.stdout.readline() == b'^[0-9.]+ Suspend$'
```
Let's walk through:
- We import the `dbusmock_system` fixture from dbusmock which provides us
with a system bus started for our test case wherever the
`dbusmock_system` argument is used by a test case and/or a pytest
fixture.
- The `upower_mock` fixture spawns the mock D-Bus server process for an initial
`/org/freedesktop/UPower` object with an `org.freedesktop.UPower`
D-Bus interface on the system bus. We capture its stdout to be
able to verify that methods were called.
We then call `org.freedesktop.DBus.Mock.AddMethod()` to add a
`Suspend()` method to our new object to the default D-Bus
interface. This will not do anything (except log its call to
stdout). It takes no input arguments, returns nothing, and does
not run any custom code.
This mock server process is yielded to the test function that uses
the `upower_mock` fixture - once the test is complete the process is
terminated again.
- `test_suspend_on_idle()` is the actual test case. It needs to run
your program in a way that should trigger one suspend call. Your
program will try to call `Suspend()`, but as that's now being
served by our mock instead of upower, there will not be any actual
machine suspend. Our mock process will log the method call
together with a time stamp; you can use the latter for doing
timing related tests, but we just ignore it here.
## Simple example from shell
We use the actual session bus for this example. You can use
`dbus-run-session` to start a private one as well if you want, but that
is not part of the actual mocking.
So let's start a mock at the D-Bus name `com.example.Foo` with an
initial "main" object on path /, with the main D-Bus interface
`com.example.Foo.Manager`:
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager
On another terminal, let's first see what it does:
gdbus introspect --session -d com.example.Foo -o /
You'll see that it supports the standard D-Bus `Introspectable` and
`Properties` interfaces, as well as the `org.freedesktop.DBus.Mock`
interface for controlling the mock, but no "real" functionality yet.
So let's add a method:
gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod '' Ping '' '' ''
Now you can see the new method in `introspect`, and call it:
gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Ping
The mock process in the other terminal will log the method call with a
time stamp, and you'll see something like `1348832614.970 Ping`.
Now add another method with two int arguments and a return value and
call it:
gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod \
'' Add 'ii' 'i' 'ret = args[0] + args[1]'
gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Add 2 3
This will print `(5,)` as expected (remember that the return value is
always a tuple), and again the mock process will log the Add method
call.
You can do the same operations in e. g. d-feet or any other D-Bus
language binding.
## Interactive debugging
It's possible to use dbus-mock to run interactive sessions using something like:
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager -e $SHELL
Where a shell session with the defined mocks is set and others can be added.
Or more complex ones such as:
python3 -m dbusmock --session -t upower -e \
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager -e \
gdbus introspect --session -d com.example.Foo -o /
## Logging
Usually you want to verify which methods have been called on the mock
with which arguments. There are three ways to do that:
- By default, the mock process writes the call log to stdout.
- You can call the mock process with the `-l`/`--logfile` argument,
or specify a log file object in the `spawn_server()` method if you
are using Python.
- You can use the `GetCalls()`, `GetMethodCalls()` and
`ClearCalls()` methods on the `org.freedesktop.DBus.Mock` D-Bus
interface to get an array of tuples describing the calls.
## Templates
Some D-Bus services are commonly used in test suites, such as UPower or
NetworkManager. python-dbusmock provides "templates" which set up the
common structure of these services (their main objects, properties, and
methods) so that you do not need to carry around this common code, and
only need to set up the particular properties and specific D-Bus objects
that you need. These templates can be parameterized for common
customizations, and they can provide additional convenience methods on
the `org.freedesktop.DBus.Mock` interface to provide more abstract
functionality like "add a battery".
For example, for starting a server with the `upower` template in
Python you can run
(self.p_mock, self.obj_upower) = self.spawn_server_template(
'upower', {'OnBattery': True}, stdout=subprocess.PIPE)
or load a template into an already running server with the
`AddTemplate()` method; this is particularly useful if you are not using
Python:
python3 -m dbusmock --system org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower
gdbus call --system -d org.freedesktop.UPower -o /org/freedesktop/UPower -m org.freedesktop.DBus.Mock.AddTemplate 'upower' '{"OnBattery": }'
This creates all expected properties such as `DaemonVersion`, and
changes the default for one of them (`OnBattery`) through the (optional)
parameters dict.
If you do not need to specify parameters, you can do this in a simpler
way with
python3 -m dbusmock --template upower
The template does not create any devices by default. You can add some
with the template's convenience methods like
ac_path = self.dbusmock.AddAC('mock_AC', 'Mock AC')
bt_path = self.dbusmock.AddChargingBattery('mock_BAT', 'Mock Battery', 30.0, 1200)
or calling `AddObject()` yourself with the desired properties, of
course.
Templates commonly implement some non-trivial functionality with actual Python
methods and the standard [dbus-python](https://dbus.freedesktop.org/doc/dbus-python/)
[`@dbus.service.method`](https://dbus.freedesktop.org/doc/dbus-python/dbus.service.html#dbus.service.method)
decorator.
To build your own template, you can copy
[dbusmock/templates/SKELETON](./dbusmock/templates/SKELETON) to your
new template file name and replace `CHANGEME` with the actual code/values.
Look at [dbusmock/templates/upower.py](./dbusmock/templates/upower.py) for
a real-life implementation.
A template can be loaded from these locations:
* Provide a path to its `.py` file. This is intended for running tests out of
git/build trees with very project specific or unstable templates.
* From [`$XDG_DATA_DIRS/python-dbusmock/templates/`*name*`.py`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
This is intended for shipping reusable templates in distribution development
packages. Load them by module name.
* python-dbusmock [ships a set of widely applicable templates](./dbusmock/templates/)
which are collaboratively maintained, like the `upower` one in the example
above. Load them by module name.
## More Examples
Have a look at the test suite for two real-live use cases:
- `tests/test_upower.py` simulates upowerd, in a more complete way
than in above example and using the `upower` template. It verifies
that `upower --dump` is convinced that it's talking to upower.
- `tests/test_api.py` runs a mock on the session bus and exercises
all available functionality, such as adding additional objects,
properties, multiple methods, input arguments, return values, code
in methods, sending signals, raising exceptions, and introspection.
## Documentation
The `dbusmock` module has extensive documentation built in, which you
can read with e. g. `pydoc3 dbusmock` or online at
https://martinpitt.github.io/python-dbusmock/
`pydoc3 dbusmock.DBusMockObject` shows the D-Bus API of the mock object,
i. e. methods like `AddObject()`, `AddMethod()` etc. which are used to
set up your mock object.
`pydoc3 dbusmock.DBusTestCase` shows the convenience Python API for
writing test cases with local private session/system buses and launching
the server.
`pydoc3 dbusmock.templates` shows all available templates.
`pydoc3 dbusmock.templates.NAME` shows the documentation and available
parameters for the `NAME` template.
`python3 -m dbusmock --help` shows the arguments and options for running
the mock server as a program.
## Development
python-dbusmock is hosted on https://github.com/martinpitt/python-dbusmock
Run the unit tests with `python3 -m unittest` or `pytest`.
In CI, the unit tests run in containers. You can run them locally with e.g.
tests/run registry.fedoraproject.org/fedora:latest
Check the [unit-tests GitHub workflow](.github/workflows/tests.yml) for the
operating systems/container images on which python-dbusmock is tested and
supported.
To debug failures interactively, run
DEBUG=1 tests/run [image]
which will sleep on failures. You can then attach to the running container
image with e.g. `podman exec -itl bash`. The `/source` directory is mounted from the
host, i.e. edit files in your normal git checkout outside of the container, and
re-run all tests in the container shell like above. You can also run a specific
test:
python3 -m unittest tests.test_api.TestAPI.test_onearg_ret
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9843738
python_dbusmock-0.37.1/dbusmock/ 0000755 0001751 0001751 00000000000 15071377700 016276 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/__init__.py 0000644 0001751 0001751 00000001762 15071377476 020427 0 ustar 00runner runner """Mock D-Bus objects for test suites."""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
from dbusmock.mockobject import MOCK_IFACE, OBJECT_MANAGER_IFACE, DBusMockObject, get_object, get_objects
from dbusmock.testcase import BusType, DBusTestCase, PrivateDBus, SpawnedMock
try:
# created by setuptools_scm
from dbusmock._version import __version__
except ImportError:
__version__ = "0.git"
__all__ = [
"MOCK_IFACE",
"OBJECT_MANAGER_IFACE",
"BusType",
"DBusMockObject",
"DBusTestCase",
"PrivateDBus",
"SpawnedMock",
"get_object",
"get_objects",
]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/__main__.py 0000644 0001751 0001751 00000013741 15071377476 020410 0 ustar 00runner runner """Main entry point for running mock server."""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import argparse
import json
import os
import platform
import subprocess
import sys
import dbusmock.mockobject
import dbusmock.testcase
def parse_args():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(description="mock D-Bus object")
parser.add_argument(
"-s",
"--system",
action="store_true",
help="put object(s) on system bus (default: session bus or template's SYSTEM_BUS flag)",
)
parser.add_argument(
"--session",
action="store_true",
help="put object(s) on session bus (default without template; overrides template's SYSTEM_BUS flag)",
)
parser.add_argument(
"-l",
"--logfile",
metavar="PATH",
help="path of log file",
)
parser.add_argument(
"-t",
"--template",
metavar="NAME",
help="template to load (instead of specifying name, path, interface)",
)
parser.add_argument(
"name",
metavar="NAME",
nargs="?",
help='D-Bus name to claim (e. g. "com.example.MyService") (if not using -t)',
)
parser.add_argument(
"path",
metavar="PATH",
nargs="?",
help="D-Bus object path for initial/main object (if not using -t)",
)
parser.add_argument(
"interface",
metavar="INTERFACE",
nargs="?",
help="main D-Bus interface name for initial object (if not using -t)",
)
parser.add_argument(
"-m",
"--is-object-manager",
action="store_true",
help="automatically implement the org.freedesktop.DBus.ObjectManager interface",
)
parser.add_argument(
"-p",
"--parameters",
help="JSON dictionary of parameters to pass to the template",
)
parser.add_argument(
"-e",
"--exec",
nargs=argparse.REMAINDER,
help="Command to run in the mock environment",
)
arguments = parser.parse_args()
if arguments.template:
if arguments.name or arguments.path or arguments.interface:
parser.error("--template and specifying NAME/PATH/INTERFACE are mutually exclusive")
else:
if not arguments.name or not arguments.path or not arguments.interface:
parser.error("Not using a template, you must specify NAME, PATH, and INTERFACE")
if arguments.system and arguments.session:
parser.error("--system and --session are mutually exclusive")
return arguments
if __name__ == "__main__":
import ctypes
import dbus.mainloop.glib
import dbus.service
args = parse_args()
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
system_bus = args.system
if args.template:
module = dbusmock.mockobject.load_module(args.template)
args.name = module.BUS_NAME
args.path = module.MAIN_OBJ
if not args.session and not args.system:
system_bus = module.SYSTEM_BUS
if hasattr(module, "IS_OBJECT_MANAGER"):
args.is_object_manager = module.IS_OBJECT_MANAGER
else:
args.is_object_manager = False
if args.is_object_manager and not hasattr(module, "MAIN_IFACE"):
args.interface = dbusmock.mockobject.OBJECT_MANAGER_IFACE
else:
args.interface = module.MAIN_IFACE
bus = dbusmock.testcase.DBusTestCase.get_dbus(system_bus)
# quit mock when the bus is going down
should_run = {True}
bus.add_signal_receiver(
should_run.pop,
signal_name="Disconnected",
path="/org/freedesktop/DBus/Local",
dbus_interface="org.freedesktop.DBus.Local",
)
bus_name = dbus.service.BusName(args.name, bus, allow_replacement=True, replace_existing=True, do_not_queue=True)
main_object = dbusmock.mockobject.DBusMockObject(
bus_name, args.path, args.interface, {}, args.logfile, args.is_object_manager
)
parameters = None
if args.parameters:
try:
parameters = json.loads(args.parameters)
except ValueError as detail:
sys.stderr.write(f"Malformed JSON given for parameters: {detail}\n")
sys.exit(2)
if not isinstance(parameters, dict):
sys.stderr.write("JSON parameters must be a dictionary\n")
sys.exit(2)
if args.template:
main_object.AddTemplate(args.template, parameters)
if platform.system() == "Darwin":
libglib = ctypes.cdll.LoadLibrary("libglib-2.0.dylib")
else:
libglib = ctypes.cdll.LoadLibrary("libglib-2.0.so.0")
dbusmock.mockobject.objects[args.path] = main_object
if args.exec:
with subprocess.Popen(args.exec) as exec_proc:
exit_status = set()
@ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_int)
def on_process_watch(_pid, status):
"""Check if the launched process is still alive"""
if os.WIFEXITED(status):
exit_status.add(os.WEXITSTATUS(status))
else:
exit_status.add(1)
should_run.pop()
libglib.g_child_watch_add(exec_proc.pid, on_process_watch)
while should_run:
libglib.g_main_context_iteration(None, True)
try:
exec_proc.terminate()
exec_proc.wait()
except ProcessLookupError:
pass
sys.exit(exit_status.pop() if exit_status else exec_proc.returncode)
else:
while should_run:
libglib.g_main_context_iteration(None, True)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903679.0
python_dbusmock-0.37.1/dbusmock/_version.py 0000644 0001751 0001751 00000000027 15071377677 020510 0 ustar 00runner runner __version__ = "0.37.1"
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/mockobject.py 0000644 0001751 0001751 00000107644 15071377476 021016 0 ustar 00runner runner """Mock D-Bus objects for test suites."""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import copy
import functools
import importlib
import importlib.util
import os
import sys
import time
import types
from pathlib import Path
from typing import Any, Dict, KeysView, List, Optional, Sequence, Tuple
from xml.etree import ElementTree as ET
import dbus
import dbus.service
# we do not use this ourselves, but mock methods often want to use this
os # pyflakes pylint: disable=pointless-statement # noqa: B018
# global path -> DBusMockObject mapping
objects: Dict[str, "DBusMockObject"] = {}
MOCK_IFACE = "org.freedesktop.DBus.Mock"
OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
PropsType = Dict[str, Any]
# (in_signature, out_signature, code, dbus_wrapper_fn)
MethodType = Tuple[str, str, str, str]
# (timestamp, method_name, call_args)
CallLogType = Tuple[int, str, Sequence[Any]]
def load_module_from_path(path: Path, template_name: str):
"""Load a mock template from a file path"""
spec = importlib.util.spec_from_file_location(template_name, path)
assert spec
mod = importlib.util.module_from_spec(spec)
exec(path.read_text("UTF-8"), mod.__dict__, mod.__dict__) # pylint: disable=exec-used
return mod
def load_module(name: str):
"""Load a mock template Python module
This can be a path to the template's .py file, a bare module name in
``$XDG_DATA_DIRS/python-dbusmock/templates/``, or a bare module name in dbusmock's shipped templates.
"""
# specified by path
pname = Path(name)
if pname.exists() and pname.suffix == ".py":
return load_module_from_path(pname, pname.stem)
# XDG_DATA_DIRS
xdg_data_dirs = os.environ.get("XDG_DATA_DIRS") or "/usr/local/share/:/usr/share/"
for d in xdg_data_dirs.split(":"):
src = Path(d, "python-dbusmock", "templates", name + ".py")
if src.exists():
return load_module_from_path(src, name)
# shipped inside dbusmock package
return importlib.import_module("dbusmock.templates." + name)
def _format_args(args):
"""Format a D-Bus argument tuple into an appropriate logging string"""
def format_arg(a):
if isinstance(a, dbus.Boolean):
return str(bool(a))
if isinstance(a, (dbus.Byte, int)):
return str(int(a))
if isinstance(a, str):
return '"' + str(a) + '"'
if isinstance(a, list):
return "[" + ", ".join([format_arg(x) for x in a]) + "]"
if isinstance(a, dict):
fmta = "{"
first = True
for k, v in a.items():
if first:
first = False
else:
fmta += ", "
fmta += format_arg(k) + ": " + format_arg(v)
return fmta + "}"
# fallback
return repr(a)
s = ""
for a in args:
if s:
s += " "
s += format_arg(a)
if s:
s = " " + s
return s
def _wrap_in_dbus_variant(value):
dbus_types = [
dbus.types.ByteArray,
dbus.types.Int16,
dbus.types.ObjectPath,
dbus.types.UInt64,
dbus.types.Boolean,
dbus.types.Int32,
dbus.types.Signature,
dbus.types.UInt16,
dbus.types.UnixFd,
dbus.types.Byte,
dbus.types.Double,
dbus.types.Int64,
dbus.types.String,
dbus.types.UInt32,
]
container_types = [
dbus.types.Array,
dbus.types.Struct,
dbus.types.Dictionary,
]
if isinstance(value, dbus.String):
return dbus.String(str(value), variant_level=1)
if type(value) in container_types:
return value
if type(value) in dbus_types:
return type(value)(value.conjugate(), variant_level=1)
if isinstance(value, str):
return dbus.String(value, variant_level=1)
raise dbus.exceptions.DBusException(f"could not wrap type {type(value)}")
def _convert_args(signature: str, args: Tuple[Any, ...]) -> List[Any]:
"""
Convert types of arguments according to signature, using
MethodCallMessage.append(); this will also provide type/length
checks, except for the case of an empty signature
"""
try:
if signature == "" and len(args) > 0:
raise TypeError("Fewer items found in D-Bus signature than in Python arguments")
m = dbus.connection.MethodCallMessage("a.b", "/a", "a.b", "a")
m.append(*args, signature=signature)
return m.get_args_list()
except Exception as e:
raise dbus.exceptions.DBusException(
f"Invalid arguments: {e!s}", name="org.freedesktop.DBus.Error.InvalidArgs"
) from e
def loggedmethod(self, func):
"""Decorator for a method to end in the call log"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
fname = func.__name__
self_arg, args = args[0], args[1:]
in_signature = getattr(func, "_dbus_in_signature", "")
args = _convert_args(in_signature, args)
self.log(fname + _format_args(args))
self.call_log.append((int(time.time()), fname, args))
self.MethodCalled(fname, args)
return func(*[self_arg, *args], **kwargs)
return wrapper
class DBusMockObject(dbus.service.Object): # pylint: disable=too-many-instance-attributes
"""Mock D-Bus object
This can be configured to have arbitrary methods (including code execution)
and properties via methods on the org.freedesktop.DBus.Mock interface, so
that you can control the mock from any programming language.
Beyond that "remote control" API, this is a standard
`dbus-python service object `_.
"""
def __init__(
self,
bus_name: str,
path: str,
interface: str,
props: PropsType,
logfile: Optional[str] = None,
is_object_manager: bool = False,
mock_data: Any = None,
) -> None:
"""Create a new DBusMockObject
:param bus_name: A dbus.service.BusName instance where the object will be put on
:param path: D-Bus object path
:param interface: Primary D-Bus interface name of this object (where
properties and methods will be put on)
:param props: A property_name (string) → property (Variant) map with initial
properties on "interface"
:param logfile: When given, method calls will be logged into that file name;
if None, logging will be written to stdout. Note that you can
also query the called methods over D-Bus with GetCalls() and
GetMethodCalls().
:param is_object_manager: If True, the GetManagedObjects method will
automatically be implemented on the object, returning
all objects which have this one's path as a prefix of
theirs. Note that the InterfacesAdded and
InterfacesRemoved signals will not be automatically
emitted.
"""
dbus.service.Object.__init__(self, bus_name, path)
self.bus_name = bus_name
self.path = path
self.interface = interface
self.is_object_manager = is_object_manager
self.object_manager: Optional[DBusMockObject] = None
self._template: Optional[str] = None
self._template_parameters: Optional[PropsType] = None
# pylint: disable=consider-using-with
self.logfile = open(logfile, "wb") if logfile else None # noqa: SIM115
self.is_logfile_owner = True
self.call_log: List[CallLogType] = []
self.mock_data = mock_data
if props is None:
props = {}
self._reset(props)
def __del__(self) -> None:
try:
if self.logfile and self.is_logfile_owner:
self.logfile.close()
except AttributeError:
pass
def _set_up_object_manager(self) -> None:
"""Set up this mock object as a D-Bus ObjectManager."""
cond = "k != '/'" if self.path == "/" else f"k.startswith('{self.path}/')"
code = f"ret = {{dbus.ObjectPath(k): objects[k].props for k in objects.keys() if {cond} }}"
self.AddMethod(OBJECT_MANAGER_IFACE, "GetManagedObjects", "", "a{oa{sa{sv}}}", code)
self.object_manager = self
def _reset(self, props: PropsType) -> None:
# interface -> name -> value
self.props = {self.interface: props}
# interface -> name -> (in_signature, out_signature, code, dbus_wrapper_fn)
self.methods: Dict[str, Dict[str, MethodType]] = {self.interface: {}}
if self.is_object_manager:
self._set_up_object_manager()
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss", out_signature="v")
def Get(self, interface_name: str, property_name: str) -> Any:
"""Standard D-Bus API for getting a property value"""
self.log(f"Get {self.path} {interface_name}.{property_name}")
if not interface_name:
interface_name = self.interface
try:
return self.GetAll(interface_name)[property_name]
except KeyError as e:
raise dbus.exceptions.DBusException(
"no such property " + property_name, name=self.interface + ".UnknownProperty"
) from e
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s", out_signature="a{sv}")
def GetAll(self, interface_name: str, *_, **__) -> PropsType:
"""Standard D-Bus API for getting all property values"""
self.log(f"GetAll {self.path} {interface_name}")
if not interface_name:
interface_name = self.interface
try:
return self.props[interface_name]
except KeyError as e:
raise dbus.exceptions.DBusException(
"no such interface " + interface_name, name=self.interface + ".UnknownInterface"
) from e
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv", out_signature="")
def Set(self, interface_name: str, property_name: str, value: Any, *_, **__) -> None:
"""Standard D-Bus API for setting a property value"""
self.log(f"Set {self.path} {interface_name}.{property_name}{_format_args((value,))}")
try:
iface_props = self.props[interface_name]
except KeyError as e:
raise dbus.exceptions.DBusException(
"no such interface " + interface_name, name=self.interface + ".UnknownInterface"
) from e
if property_name not in iface_props:
raise dbus.exceptions.DBusException(
"no such property " + property_name, name=self.interface + ".UnknownProperty"
)
iface_props[property_name] = value
self.EmitSignal(
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
"sa{sv}as",
[interface_name, dbus.Dictionary({property_name: value}, signature="sv"), dbus.Array([], signature="s")],
)
@dbus.service.method(MOCK_IFACE, in_signature="ssa{sv}a(ssss)", out_signature="")
def AddObject(
self, path: str, interface: str, properties: PropsType, methods: List[MethodType], **kwargs
) -> None:
"""Dynamically add a new D-Bus object to the mock
:param path: D-Bus object path
:param interface: Primary D-Bus interface name of this object (where
properties and methods will be put on)
:param properties: A property_name (string) → value map with initial
properties on "interface"
:param methods: An array of 4-tuples (name, in_sig, out_sig, code) describing
methods to add to "interface"; see AddMethod() for details of
the tuple values
Keyword Arguments:
:param mock_class: A DBusMockObject derived class name (this is only possible
when the method is not called via dbus)
:param mock_data: Additional data which will be passed to the DBusMockObject
constructor
If this is a D-Bus ObjectManager instance, the InterfacesAdded signal
will *not* be emitted for the object automatically; it must be emitted
manually if desired. This is because AddInterface may be called after
AddObject, but before the InterfacesAdded signal should be emitted.
Example::
dbus_proxy.AddObject('/com/example/Foo/Manager',
'com.example.Foo.Control',
{
'state': dbus.String('online'),
},
[
('Start', '', '', ''),
('EchoInt', 'i', 'i', 'ret = args[0]'),
('GetClients', '', 'ao', 'ret = ["/com/example/Foo/Client1"]'),
])
Example 2 (in a template)::
class FooManager(dbusmock.mockobject.DBusMockObject):
def __init__(self, *args, **kwargs):
super(FooManager, self).__init__(*args, **kwargs)
self.magic = kwargs.get('mock_data')
@dbus.service.method('com.example.Foo.Control', in_signature='i', out_signature='i')
def EchoMagic(self, input):
return self.magic + input
dbus_proxy.AddObject('/com/example/Foo/Manager',
'com.example.Foo.Control',
{}, [],
mock_class=FooManager,
mock_data=42)
"""
if path in objects:
raise dbus.exceptions.DBusException(
f"object {path} already exists", name="org.freedesktop.DBus.Mock.NameError"
)
mock_class = kwargs.get("mock_class", DBusMockObject)
mock_data = kwargs.get("mock_data")
obj = mock_class(self.bus_name, path, interface, properties, mock_data=mock_data)
# make sure created objects inherit the log file stream
obj.logfile = self.logfile
obj.object_manager = self.object_manager
obj.is_logfile_owner = False
obj.AddMethods(interface, methods)
objects[path] = obj
@dbus.service.method(MOCK_IFACE, in_signature="s", out_signature="")
def RemoveObject(self, path: str) -> None:
"""Remove a D-Bus object from the mock
As with AddObject, this will *not* emit the InterfacesRemoved signal if
it's an ObjectManager instance.
"""
try:
objects[path].remove_from_connection()
del objects[path]
except KeyError as e:
raise dbus.exceptions.DBusException(
f"object {path} does not exist", name="org.freedesktop.DBus.Mock.NameError"
) from e
@dbus.service.method(MOCK_IFACE, in_signature="", out_signature="")
def Reset(self) -> None:
"""Reset the mock object state.
Remove all mock objects from the bus and tidy up so the state is as if
python-dbusmock had just been restarted. If the mock object was
originally created with a template (from the command line, the Python
API or by calling AddTemplate over D-Bus), it will be
re-instantiated with that template.
"""
# Clear other existing objects.
for obj_name, obj in objects.items():
if obj_name != self.path:
obj.remove_from_connection()
objects.clear()
# Reinitialise our state. Carefully remove new methods from our dict;
# they don't not actually exist if they are a statically defined
# template function
for method_name in self.methods[self.interface]:
try:
delattr(self.__class__, method_name)
except AttributeError:
pass
self._reset({})
if self._template is not None:
self.AddTemplate(self._template, self._template_parameters)
objects[self.path] = self
@dbus.service.method(MOCK_IFACE, in_signature="sssss", out_signature="")
def AddMethod(self, interface, name: str, in_sig: str, out_sig: str, code: str) -> None:
"""Dynamically add a method to this object
:param interface: D-Bus interface to add this to. For convenience you can
specify '' here to add the method to the object's main
interface (as specified on construction).
:param name: Name of the method
:param in_sig: Signature of input arguments; for example "ias" for a method
that takes an int32 and a string array as arguments; see
`the DBus spec `_.
:param out_sig: Signature of output arguments; for example "s" for a method
that returns a string; use '' for methods that do not return
anything.
:param code: Python 3 code to run in the method call; you have access to the
arguments through the "args" list, and can set the return value
by assigning a value to the "ret" variable. You can also read the
global "objects" variable, which is a dictionary mapping object
paths to DBusMockObject instances.
For keeping state across method calls, you are free to use normal
Python members of the "self" object, which will be persistent for
the whole mock's life time. E. g. you can have a method with
"self.my_state = True", and another method that returns it with
"ret = self.my_state".
Methods can raise exceptions in the usual way, in particular
`dbus.exceptions.DBusException `_.
When specifying '', the method will not do anything (except
logging) and return None.
This is meant for adding a method to a mock at runtime, from any programming language.
You can also use it in templates in the load() function.
For implementing non-trivial and static methods in templates, it is recommended to
implement them in the normal dbus-python way with using the @dbus.service.method
decorator instead.
"""
# pylint: disable=protected-access
if not interface:
interface = self.interface
n_args = len(dbus.Signature(in_sig))
# we need to have separate methods for dbus-python, so clone
# mock_method(); using message_keyword with this dynamic approach fails
# because inspect cannot handle those, so pass on interface and method
# name as first positional arguments
# pylint: disable=unnecessary-lambda-assignment
method = lambda self, *args, **kwargs: DBusMockObject.mock_method( # noqa: E731
self, interface, name, in_sig, *args, **kwargs
)
# we cannot specify in_signature here, as that trips over a consistency
# check in dbus-python; we need to set it manually instead
dbus_method = dbus.service.method(interface, out_signature=out_sig)(method)
dbus_method.__name__ = str(name)
dbus_method._dbus_in_signature = in_sig
dbus_method._dbus_args = [f"arg{i}" for i in range(1, n_args + 1)]
# for convenience, add mocked methods on the primary interface as
# callable methods
if interface == self.interface:
setattr(self.__class__, name, dbus_method)
self.methods.setdefault(interface, {})[str(name)] = (in_sig, out_sig, code, dbus_method)
@dbus.service.method(MOCK_IFACE, in_signature="sa(ssss)", out_signature="")
def AddMethods(self, interface: str, methods: List[MethodType]) -> None:
"""Add several methods to this object
:param interface: D-Bus interface to add this to. For convenience you can
specify '' here to add the method to the object's main
interface (as specified on construction).
:param methods: list of 4-tuples (name, in_sig, out_sig, code) describing one
method each. See AddMethod() for details of the tuple values.
"""
for method in methods:
self.AddMethod(interface, *method)
def _set_property(self, interface, name, value):
# copy.copy removes one level of variant-ness, which means that the
# types get exported in introspection data correctly, but we can't do
# this for container types.
if not isinstance(value, (dbus.Dictionary, dbus.Array)):
value = copy.copy(value)
self.props.setdefault(interface, {})[name] = value
@dbus.service.method(MOCK_IFACE, in_signature="sa{sv}", out_signature="")
def UpdateProperties(self, interface: str, properties: PropsType) -> None:
"""Update properties on this object and send a PropertiesChanged signal
:param interface: D-Bus interface to update this to. For convenience you can
specify '' here to add the property to the object's main
interface (as specified on construction).
:param properties: A property_name (string) → value map
"""
changed_props = {}
for name, value in properties.items():
if not interface:
interface = self.interface
if name not in self.props.get(interface, {}):
raise dbus.exceptions.DBusException(f"property {name} not found", name=interface + ".NoSuchProperty")
self._set_property(interface, name, value)
changed_props[name] = _wrap_in_dbus_variant(value)
self.EmitSignal(dbus.PROPERTIES_IFACE, "PropertiesChanged", "sa{sv}as", [interface, changed_props, []])
@dbus.service.method(MOCK_IFACE, in_signature="ssv", out_signature="")
def AddProperty(self, interface: str, name: str, value: Any) -> None:
"""Add property to this object
:param interface: D-Bus interface to add this to. For convenience you can
specify '' here to add the property to the object's main
interface (as specified on construction).
:param name: Property name.
:param value: Property value.
"""
if not interface:
interface = self.interface
if name in self.props.get(interface, {}):
raise dbus.exceptions.DBusException(
f"property {name} already exists", name=self.interface + ".PropertyExists"
)
self._set_property(interface, name, value)
@dbus.service.method(MOCK_IFACE, in_signature="sa{sv}", out_signature="")
def AddProperties(self, interface: str, properties: PropsType) -> None:
"""Add several properties to this object
:param interface: D-Bus interface to add this to. For convenience you can
specify '' here to add the property to the object's main
interface (as specified on construction).
:param properties: A property_name (string) → value map
"""
for k, v in properties.items():
self.AddProperty(interface, k, v)
@dbus.service.method(MOCK_IFACE, in_signature="sa{sv}", out_signature="")
def AddTemplate(self, template: str, parameters: PropsType) -> None:
"""Load a template into the mock.
python-dbusmock ships a set of standard mocks for common system
services such as UPower and NetworkManager. With these the actual tests
become a lot simpler, as they only have to set up the particular
properties for the tests, and not the skeleton of common properties,
interfaces, and methods.
:param template: Name of the template to load or the full path to a *.py file
for custom templates. See "pydoc dbusmock.templates" for a
list of available templates from python-dbusmock package, and
"pydoc dbusmock.templates.NAME" for documentation about
template NAME.
:param parameters: A parameter (string) → value (variant) map, for
parameterizing templates. Each template can define their
own, see documentation of that particular template for
details.
"""
try:
module = load_module(template)
except ImportError as e:
raise dbus.exceptions.DBusException(
f"Cannot add template {template}: {e!s}", name="org.freedesktop.DBus.Mock.TemplateError"
) from e
# If the template specifies this is an ObjectManager, set that up
if hasattr(module, "IS_OBJECT_MANAGER") and module.IS_OBJECT_MANAGER:
self._set_up_object_manager()
# pick out all D-Bus service methods and add them to our interface
for symbol in dir(module):
# pylint: disable=protected-access
fn = getattr(module, symbol)
if "_dbus_interface" in dir(fn) and ("_dbus_is_signal" not in dir(fn) or not fn._dbus_is_signal):
fn = loggedmethod(self, fn)
# for dbus-python compatibility, add methods as callables
setattr(self.__class__, symbol, fn)
self.methods.setdefault(fn._dbus_interface, {})[str(symbol)] = (
fn._dbus_in_signature,
fn._dbus_out_signature,
"",
fn,
)
if parameters is None:
parameters = {}
module.load(self, parameters)
# save the given template and parameters for re-instantiation on
# Reset()
self._template = template
self._template_parameters = parameters
def _emit_signal(
self, interface: str, name: str, signature: str, sigargs: Tuple[Any, ...], details: PropsType
) -> None:
# pylint: disable=protected-access
if not interface:
interface = self.interface
args = _convert_args(signature, sigargs)
path = details.get("path", self.path)
sig = dbus.lowlevel.SignalMessage(path, interface, name)
sig.append(*args, signature=signature)
dest = details.get("destination", None)
if dest is not None:
sig.set_destination(dest)
for location in self.locations:
conn = location[0]
conn.send_message(sig)
self.log(f"emit {path} {interface}.{name}{_format_args(args)}")
@dbus.service.method(MOCK_IFACE, in_signature="sssav", out_signature="")
def EmitSignal(self, interface: str, name: str, signature: str, sigargs: Tuple[Any, ...]) -> None:
"""Emit a signal from the object.
:param interface: D-Bus interface to send the signal from. For convenience you
can specify '' here to add the method to the object's main
interface (as specified on construction).
:param name: Name of the signal
:param signature: Signature of input arguments; for example "ias" for a signal
that takes an int32 and a string array as arguments; see
http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-signatures
:param args: variant array with signal arguments; must match order and type in
"signature"
"""
self._emit_signal(interface, name, signature, sigargs, {})
@dbus.service.method(MOCK_IFACE, in_signature="sssava{sv}", out_signature="")
def EmitSignalDetailed(
self, interface: str, name: str, signature: str, sigargs: Tuple[Any, ...], details: PropsType
) -> None:
"""Emit a signal from the object with extra details.
:param interface: D-Bus interface to send the signal from. For convenience you
can specify '' here to add the method to the object's main
interface (as specified on construction).
:param name: Name of the signal
:param signature: Signature of input arguments; for example "ias" for a signal
that takes an int32 and a string array as arguments; see
http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-signatures
:param args: variant array with signal arguments; must match order and type in
"signature"
:param details: dictionary with a string key/value entries. Supported keys are:
"destination": for the signal destination
"path": for the object path to send the signal from
"""
self._emit_signal(interface, name, signature, sigargs, details)
@dbus.service.method(MOCK_IFACE, in_signature="", out_signature="a(tsav)")
def GetCalls(self) -> List[CallLogType]:
"""List all the logged calls since the last call to ClearCalls().
Return a list of (timestamp, method_name, args_list) tuples.
"""
return self.call_log
@dbus.service.method(MOCK_IFACE, in_signature="s", out_signature="a(tav)")
def GetMethodCalls(self, method: str) -> List[Tuple[int, Sequence[Any]]]:
"""List all the logged calls of a particular method.
Return a list of (timestamp, args_list) tuples.
"""
return [(row[0], row[2]) for row in self.call_log if row[1] == method]
@dbus.service.method(MOCK_IFACE, in_signature="", out_signature="")
def ClearCalls(self) -> None:
"""Empty the log of mock call signatures."""
self.call_log = []
@dbus.service.signal(MOCK_IFACE, signature="sav")
def MethodCalled(self, name, args):
"""Signal emitted for every called mock method.
This is emitted for all mock method calls. This can be used to confirm
that a particular method was called with particular arguments, as an
alternative to reading the mock's log or GetCalls().
"""
def object_manager_emit_added(self, path: str) -> None:
"""Emit ObjectManager.InterfacesAdded signal"""
if self.object_manager is not None:
self.object_manager.EmitSignal(
OBJECT_MANAGER_IFACE, "InterfacesAdded", "oa{sa{sv}}", [dbus.ObjectPath(path), objects[path].props]
)
def object_manager_emit_removed(self, path: str) -> None:
"""Emit ObjectManager.InterfacesRemoved signal"""
if self.object_manager is not None:
self.object_manager.EmitSignal(
OBJECT_MANAGER_IFACE, "InterfacesRemoved", "oas", [dbus.ObjectPath(path), objects[path].props]
)
def mock_method(self, interface: str, dbus_method: str, in_signature: str, *m_args, **_) -> Any:
"""Master mock method.
This gets "instantiated" in AddMethod(). Execute the code snippet of
the method and return the "ret" variable if it was set.
"""
# print('mock_method', dbus_method, self, in_signature, args, _, file=sys.stderr)
try:
args = _convert_args(in_signature, m_args)
self.log(dbus_method + _format_args(args))
self.call_log.append((int(time.time()), str(dbus_method), args))
self.MethodCalled(dbus_method, args)
# The code may be a Python 3 string to interpret, or may be a function
# object (if AddMethod was called from within Python itself, rather than
# over D-Bus).
code = self.methods[interface][dbus_method][2]
if code and isinstance(code, types.FunctionType):
return code(self, *args)
if code:
loc = locals().copy()
exec(code, globals(), loc) # pylint: disable=exec-used
if "ret" in loc:
return loc["ret"]
except Exception as e:
self.log(dbus_method + " raised: " + str(e))
raise e
return None
def log(self, msg: str) -> None:
"""Log a message, prefixed with a timestamp.
If a log file was specified in the constructor, it is written there,
otherwise it goes to stdout.
"""
fd = self.logfile.fileno() if self.logfile else sys.stdout.fileno()
os.write(fd, f"{time.time():.3f} {msg}\n".encode())
@dbus.service.method(
dbus.INTROSPECTABLE_IFACE,
in_signature="",
out_signature="s",
path_keyword="object_path",
connection_keyword="connection",
)
def Introspect(self, object_path: str, connection: dbus.connection.Connection) -> str:
"""Return XML description of this object's interfaces, methods and signals.
This wraps dbus-python's Introspect() method to include the dynamic
methods and properties.
"""
# _dbus_class_table is an indirect private member of dbus.service.Object that pylint fails to see
# pylint: disable=no-member
# temporarily add our dynamic methods
cls = self.__class__.__module__ + "." + self.__class__.__name__
orig_interfaces = self._dbus_class_table[cls]
mock_interfaces = orig_interfaces.copy()
for iface, methods in self.methods.items():
for method, impl in methods.items():
mock_interfaces.setdefault(iface, {})[method] = impl[3]
self._dbus_class_table[cls] = mock_interfaces
xml = dbus.service.Object.Introspect(self, object_path, connection)
tree = ET.fromstring(xml)
for name, name_props in self.props.items():
# We might have properties for new interfaces we don't know about
# yet. Try to find an existing node named after our
# interface to append to, and create one if we can't.
interface = tree.find(f".//interface[@name='{name}']")
if interface is None:
interface = ET.Element("interface", {"name": name})
tree.append(interface)
for prop, val in name_props.items():
if val is None:
# can't guess type from None, skip
continue
elem = ET.Element(
"property",
{
"name": prop,
# We don't store the signature anywhere, so guess it.
"type": dbus.lowlevel.Message.guess_signature(val),
"access": "readwrite",
},
)
interface.append(elem)
xml = ET.tostring(tree, encoding="utf8", method="xml").decode("utf8")
# restore original class table
self._dbus_class_table[cls] = orig_interfaces
return xml
# Overwrite dbus-python's _method_lookup(), as that offers no way to have the
# same method name on different interfaces
orig_method_lookup = dbus.service._method_lookup # pylint: disable=protected-access
def _dbusmock_method_lookup(obj, method_name, dbus_interface):
try:
m = obj.methods[dbus_interface or obj.interface][method_name]
return (m[3], m[3])
except KeyError:
return orig_method_lookup(obj, method_name, dbus_interface)
dbus.service._method_lookup = _dbusmock_method_lookup # pylint: disable=protected-access
#
# Helper API for templates
#
def get_objects() -> KeysView[str]:
"""Return all existing object paths"""
return objects.keys()
def get_object(path) -> DBusMockObject:
"""Return object for a given object path"""
return objects[path]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/pytest_fixtures.py 0000644 0001751 0001751 00000001765 15071377476 022154 0 ustar 00runner runner """pytest fixtures for DBusMock"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = "(c) 2023 Martin Pitt "
from typing import Iterator
import pytest
from dbusmock.testcase import BusType, PrivateDBus
@pytest.fixture(scope="session")
def dbusmock_system() -> Iterator[PrivateDBus]:
"""Export the whole DBusTestCase as a fixture, with the system bus started"""
with PrivateDBus(BusType.SYSTEM) as bus:
yield bus
@pytest.fixture(scope="session")
def dbusmock_session() -> Iterator[PrivateDBus]:
"""Export the whole DBusTestCase as a fixture, with the session bus started"""
with PrivateDBus(BusType.SESSION) as bus:
yield bus
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9873736
python_dbusmock-0.37.1/dbusmock/templates/ 0000755 0001751 0001751 00000000000 15071377700 020274 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/SKELETON 0000644 0001751 0001751 00000004456 15071377476 021466 0 ustar 00runner runner '''CHANGEME mock template
This creates the expected methods and properties of the main
CHANGEME object, but no devices. You can specify any property
such as CHANGEME in "parameters".
'''
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = 'CHANGEME'
__copyright__ = 'CHANGEME'
import dbus
from dbusmock import MOCK_IFACE
# False for session bus, True for system bus; if not present, the bus has to be
# specified in the spawn_server_template() call
SYSTEM_BUS = True # CHANGEME
BUS_NAME = 'org.freedesktop.CHANGEME'
MAIN_OBJ = '/org/freedesktop/CHANGEME'
# If your top-level object is an org.freedesktop.DBus.ObjectManager, you can
# skip setting MAIN_IFACE and set IS_OBJECT_MANAGER to True; then dbusmock will
# automatically provide the GetManagedObjects() API. In all other cases,
# specify the interface name of the main object here.
MAIN_IFACE = 'org.freedesktop.CHANGEME'
# IS_OBJECT_MANAGER = True
def load(mock, parameters):
mock.AddMethods(MAIN_IFACE, [
# CHANGEME: Add some methods if required, otherwise drop the AddMethods call
('CHANGEME', '', 'b', 'ret = %s' % parameters.get('CHANGEME', True)),
])
mock.AddProperties(MAIN_IFACE,
dbus.Dictionary({
# CHANGEME: Add properties if required, otherwise
# drop this call
'MyProperty': parameters.get('MyProperty', 'CHANGEME'),
}, signature='sv'))
# CHANGEME: You can add convenience methods to the org.freedesktop.DBus.Mock
# interface to provide abstract functionality such as adding specific devices
@dbus.service.method(MOCK_IFACE,
in_signature='ss', out_signature='s')
def AddCHANGEME(self, device_name, _CHANGEME):
'''Convenience method to add a CHANGEME object
You have to specify a ...
Please note that this does not set any global properties.
Returns the new object path.
'''
path = '/org/freedesktop/CHANGEME/' + device_name
self.AddObject(path, ...)
return path
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/__init__.py 0000644 0001751 0001751 00000000576 15071377476 022427 0 ustar 00runner runner """Mock templates for common D-Bus services"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/bluez5-obex.py 0000644 0001751 0001751 00000024133 15071377476 023024 0 ustar 00runner runner """obexd mock template
This creates the expected methods and properties of the object manager
org.bluez.obex object (/), the manager object (/org/bluez/obex), but no agents
or clients.
This supports BlueZ 5 only.
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Philip Withnall"
__copyright__ = """
(c) 2013 Collabora Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import tempfile
from pathlib import Path
import dbus
from dbusmock import OBJECT_MANAGER_IFACE, mockobject
BUS_NAME = "org.bluez.obex"
MAIN_OBJ = "/"
SYSTEM_BUS = False
IS_OBJECT_MANAGER = True
OBEX_MOCK_IFACE = "org.bluez.obex.Mock"
AGENT_MANAGER_IFACE = "org.bluez.AgentManager1"
CLIENT_IFACE = "org.bluez.obex.Client1"
SESSION_IFACE = "org.bluez.obex.Session1"
PHONEBOOK_ACCESS_IFACE = "org.bluez.obex.PhonebookAccess1"
TRANSFER_IFACE = "org.bluez.obex.Transfer1"
TRANSFER_MOCK_IFACE = "org.bluez.obex.transfer1.Mock"
def load(mock, _parameters):
mock.AddObject(
"/org/bluez/obex",
AGENT_MANAGER_IFACE,
{},
[
("RegisterAgent", "os", "", ""),
("UnregisterAgent", "o", "", ""),
],
)
obex = mockobject.objects["/org/bluez/obex"]
obex.AddMethods(
CLIENT_IFACE,
[
("CreateSession", "sa{sv}", "o", CreateSession),
("RemoveSession", "o", "", RemoveSession),
],
)
@dbus.service.method(CLIENT_IFACE, in_signature="sa{sv}", out_signature="o")
def CreateSession(self, destination, args):
"""OBEX method to create a new transfer session.
The destination must be the address of the destination Bluetooth device.
The given arguments must be a map from well-known keys to values,
containing at least the `Target` key, whose value must be `PBAP` (other
keys and values are accepted by the real daemon, but not by this mock
daemon at the moment). If the target is missing or incorrect, an
Unsupported error is returned on the bus.
Returns the path of a new Session object.
"""
if "Target" not in args or args["Target"].upper() != "PBAP":
raise dbus.exceptions.DBusException(
"Non-PBAP targets are not currently supported by this python-dbusmock template.",
name=OBEX_MOCK_IFACE + ".Unsupported",
)
# Find the first unused session ID.
client_path = "/org/bluez/obex/client"
session_id = 0
while client_path + "/session" + str(session_id) in mockobject.objects:
session_id += 1
path = client_path + "/session" + str(session_id)
properties = {
"Source": dbus.String("FIXME"),
"Destination": dbus.String(destination),
"Channel": dbus.Byte(0),
"Target": dbus.String("FIXME"),
"Root": dbus.String("FIXME"),
}
self.AddObject(
path,
SESSION_IFACE,
# Properties
properties,
# Methods
[
("GetCapabilities", "", "s", ""), # Currently a no-op
],
)
session = mockobject.objects[path]
session.AddMethods(
PHONEBOOK_ACCESS_IFACE,
[
("Select", "ss", "", ""), # Currently a no-op
# Currently a no-op
("List", "a{sv}", "a(ss)", 'ret = dbus.Array(signature="(ss)")'),
# Currently a no-op
("ListFilterFields", "", "as", 'ret = dbus.Array(signature="(s)")'),
("PullAll", "sa{sv}", "sa{sv}", PullAll),
("GetSize", "", "q", "ret = 0"),
],
)
manager = mockobject.objects["/"]
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesAdded",
"oa{sa{sv}}",
[
dbus.ObjectPath(path),
{SESSION_IFACE: properties},
],
)
return path
@dbus.service.method(CLIENT_IFACE, in_signature="o", out_signature="")
def RemoveSession(self, session_path):
"""OBEX method to remove an existing transfer session.
This takes the path of the transfer Session object and removes it.
"""
manager = mockobject.objects["/"]
# Remove all the session's transfers.
transfer_id = 0
while session_path + "/transfer" + str(transfer_id) in mockobject.objects:
transfer_path = session_path + "/transfer" + str(transfer_id)
transfer_id += 1
self.RemoveObject(transfer_path)
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesRemoved",
"oas",
[
dbus.ObjectPath(transfer_path),
[TRANSFER_IFACE],
],
)
# Remove the session itself.
self.RemoveObject(session_path)
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesRemoved",
"oas",
[
dbus.ObjectPath(session_path),
[SESSION_IFACE, PHONEBOOK_ACCESS_IFACE],
],
)
@dbus.service.method(PHONEBOOK_ACCESS_IFACE, in_signature="sa{sv}", out_signature="sa{sv}")
def PullAll(self, target_file, filters):
"""OBEX method to start a pull transfer of a phone book.
This doesn't complete the transfer; code to mock up activating and
completing the transfer must be provided by the test driver, as it is
too complex and test-specific to put here.
The target_file is the absolute path for a file which will have zero or
more vCards, separated by new-line characters, written to it if the method
is successful (and the transfer is completed). This target_file is actually
emitted in a TransferCreated signal, which is a special part of the mock
interface designed to be handled by the test driver, which should then
populate that file and call UpdateStatus on the Transfer object. The test
driver is responsible for deleting the file once the test is complete.
The filters parameter is a map of filters to be applied to the results
device-side before transmitting them back to the adapter.
Returns a tuple containing the path for a new Transfer D-Bus object
representing the transfer, and a map of the initial properties of that
Transfer object.
"""
# Find the first unused session ID.
session_path = self.path
transfer_id = 0
while session_path + "/transfer" + str(transfer_id) in mockobject.objects:
transfer_id += 1
transfer_path = session_path + "/transfer" + str(transfer_id)
# Create a new temporary file to transfer to.
with tempfile.NamedTemporaryFile(suffix=".vcf", prefix="tmp-bluez5-obex-PullAll_", delete=False) as temp_file:
filename = Path(temp_file.name).resolve()
props = {
"Status": dbus.String("queued"),
"Session": dbus.ObjectPath(session_path),
"Name": dbus.String(target_file),
"Filename": dbus.String(filename),
"Transferred": dbus.UInt64(0),
}
self.AddObject(
transfer_path,
TRANSFER_IFACE,
# Properties
props,
# Methods
[
("Cancel", "", "", ""), # Currently a no-op
],
)
transfer = mockobject.objects[transfer_path]
transfer.AddMethods(
TRANSFER_MOCK_IFACE,
[
("UpdateStatus", "b", "", UpdateStatus),
],
)
manager = mockobject.objects["/"]
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesAdded",
"oa{sa{sv}}",
[
dbus.ObjectPath(transfer_path),
{TRANSFER_IFACE: props},
],
)
# Emit a behind-the-scenes signal that a new transfer has been created.
manager.EmitSignal(OBEX_MOCK_IFACE, "TransferCreated", "sa{sv}s", [transfer_path, filters, str(filename)])
return (transfer_path, props)
@dbus.service.signal(OBEX_MOCK_IFACE, signature="sa{sv}s")
def TransferCreated(_self, _path, _filters, _transfer_filename):
"""Mock signal emitted when a new Transfer object is created.
This is not part of the BlueZ OBEX interface; it is purely for use by test
driver code. It is emitted by the PullAll method, and is intended to be
used as a signal to call UpdateStatus on the newly created Transfer
(potentially after a timeout).
The path is of the new Transfer object, and the filters are as provided to
PullAll. The transfer filename is the full path and filename of a newly
created empty temporary file which the test driver should write to.
The test driver should then call UpdateStatus on the Transfer object each
time a write is made to the transfer file. The final call to UpdateStatus
should mark the transfer as complete.
The test driver is responsible for deleting the transfer file once the test
is complete.
FIXME: Ideally the UpdateStatus method would only be used for completion,
and all intermediate updates would be found by watching the size of the
transfer file. However, that means adding a dependency on an inotify
package, which seems a little much.
"""
@dbus.service.method(TRANSFER_MOCK_IFACE, in_signature="b", out_signature="")
def UpdateStatus(self, is_complete):
"""Mock method to update the transfer status.
If is_complete is False, this marks the transfer is active; otherwise it
marks the transfer as complete. It is an error to call this method after
calling it with is_complete as True.
In both cases, it updates the number of bytes transferred to be the current
size of the transfer file (whose filename was emitted in the
TransferCreated signal).
"""
status = "complete" if is_complete else "active"
transferred = Path(self.props[TRANSFER_IFACE]["Filename"]).stat().st_size
self.props[TRANSFER_IFACE]["Status"] = status
self.props[TRANSFER_IFACE]["Transferred"] = dbus.UInt64(transferred)
self.EmitSignal(
dbus.PROPERTIES_IFACE,
"PropertiesChanged",
"sa{sv}as",
[
TRANSFER_IFACE,
{
"Status": dbus.String(status),
"Transferred": dbus.UInt64(transferred),
},
[],
],
)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/bluez5.py 0000644 0001751 0001751 00000070054 15071377476 022074 0 ustar 00runner runner """bluetoothd mock template
This creates the expected methods and properties of the object manager
org.bluez object (/), the manager object (/org/bluez), but no adapters or
devices.
This supports BlueZ 5 only.
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Philip Withnall"
__copyright__ = """
(c) 2013 Collabora Ltd.
(c) 2017 - 2022 Martin Pitt
"""
from pathlib import Path
import dbus
from dbusmock import OBJECT_MANAGER_IFACE, mockobject
BUS_NAME = "org.bluez"
MAIN_OBJ = "/"
SYSTEM_BUS = True
IS_OBJECT_MANAGER = True
BLUEZ_MOCK_IFACE = "org.bluez.Mock"
AGENT_MANAGER_IFACE = "org.bluez.AgentManager1"
PROFILE_MANAGER_IFACE = "org.bluez.ProfileManager1"
ADAPTER_IFACE = "org.bluez.Adapter1"
MEDIA_IFACE = "org.bluez.Media1"
NETWORK_SERVER_IFACE = "org.bluez.Network1"
DEVICE_IFACE = "org.bluez.Device1"
LE_ADVERTISING_MANAGER_IFACE = "org.bluez.LEAdvertisingManager1"
LE_ADVERTISEMENT_IFACE = "org.bluez.LEAdvertisement1"
ADVERTISEMENT_MONITOR_MANAGER_IFACE = "org.bluez.AdvertisementMonitorManager1"
ADVERTISEMENT_MONITOR_IFACE = "org.bluez.AdvertisementMonitor1"
# The device class of some arbitrary Android phone.
MOCK_PHONE_CLASS = 5898764
# Maximum number of BLE advertisements supported per adapter.
MAX_ADVERTISEMENT_INSTANCES = 5
@dbus.service.method(AGENT_MANAGER_IFACE, in_signature="os", out_signature="")
def RegisterAgent(manager, agent_path, capability):
all_caps = ["DisplayOnly", "DisplayYesNo", "KeyboardOnly", "NoInputNoOutput", "KeyboardDisplay"]
if agent_path in manager.agent_paths:
raise dbus.exceptions.DBusException(
"Another agent is already registered " + agent_path, name="org.bluez.Error.AlreadyExists"
)
# Fallback to "KeyboardDisplay" as per BlueZ spec
if not capability:
capability = "KeyboardDisplay"
if capability not in all_caps:
raise dbus.exceptions.DBusException(
"Unsupported capability " + capability, name="org.bluez.Error.InvalidArguments"
)
if not manager.default_agent:
manager.default_agent = agent_path
manager.agent_paths += [agent_path]
manager.capabilities[str(agent_path)] = capability
@dbus.service.method(AGENT_MANAGER_IFACE, in_signature="o", out_signature="")
def UnregisterAgent(manager, agent_path):
if agent_path not in manager.agent_paths:
raise dbus.exceptions.DBusException("Agent not registered " + agent_path, name="org.bluez.Error.DoesNotExist")
manager.agent_paths.remove(agent_path)
del manager.capabilities[agent_path]
if manager.default_agent == agent_path:
if len(manager.agent_paths) > 0:
manager.default_agent = manager.agent_paths[-1]
else:
manager.default_agent = None
@dbus.service.method(AGENT_MANAGER_IFACE, in_signature="o", out_signature="")
def RequestDefaultAgent(manager, agent_path):
if agent_path not in manager.agent_paths:
raise dbus.exceptions.DBusException("Agent not registered " + agent_path, name="org.bluez.Error.DoesNotExist")
manager.default_agent = agent_path
def load(mock, parameters):
mock.AddObject(
"/org/bluez",
AGENT_MANAGER_IFACE,
{},
[
("RegisterAgent", "os", "", RegisterAgent),
("RequestDefaultAgent", "o", "", RequestDefaultAgent),
("UnregisterAgent", "o", "", UnregisterAgent),
],
)
bluez = mockobject.objects["/org/bluez"]
bluez.AddMethods(
PROFILE_MANAGER_IFACE,
[
("RegisterProfile", "osa{sv}", "", ""),
("UnregisterProfile", "o", "", ""),
],
)
bluez.agent_paths = []
bluez.capabilities = {}
bluez.default_agent = None
# whether to expose the LEAdvertisingManager1 interface on adapters (BLE advertising)
bluez.enable_advertise_api = parameters.get("enable_advertise_api", True)
# whether to expose the AdvertisementMonitorManager1 interface on adapters (Passive scanning)
bluez.enable_monitor_api = parameters.get("enable_monitor_api", True)
@dbus.service.method(ADAPTER_IFACE, in_signature="o", out_signature="")
def RemoveDevice(adapter, path):
adapter.RemoveObject(path)
manager = mockobject.objects["/"]
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesRemoved",
"oas",
[
dbus.ObjectPath(path),
[DEVICE_IFACE],
],
)
@dbus.service.method(ADAPTER_IFACE, in_signature="", out_signature="")
def StartDiscovery(adapter):
adapter.props[ADAPTER_IFACE]["Discovering"] = True
# NOTE: discovery filter support is minimal to mock
# the Discoverable discovery filter
if adapter.props[ADAPTER_IFACE]["DiscoveryFilter"] is not None:
adapter.props[ADAPTER_IFACE]["Discoverable"] = True
adapter.EmitSignal(
dbus.PROPERTIES_IFACE,
"PropertiesChanged",
"sa{sv}as",
[
ADAPTER_IFACE,
{
"Discoverable": dbus.Boolean(adapter.props[ADAPTER_IFACE]["Discoverable"]),
"Discovering": dbus.Boolean(adapter.props[ADAPTER_IFACE]["Discovering"]),
},
[],
],
)
@dbus.service.method(ADAPTER_IFACE, in_signature="", out_signature="")
def StopDiscovery(adapter):
adapter.props[ADAPTER_IFACE]["Discovering"] = False
# NOTE: discovery filter support is minimal to mock
# the Discoverable discovery filter
if adapter.props[ADAPTER_IFACE]["DiscoveryFilter"] is not None:
adapter.props[ADAPTER_IFACE]["Discoverable"] = False
adapter.EmitSignal(
dbus.PROPERTIES_IFACE,
"PropertiesChanged",
"sa{sv}as",
[
ADAPTER_IFACE,
{
"Discoverable": dbus.Boolean(adapter.props[ADAPTER_IFACE]["Discoverable"]),
"Discovering": dbus.Boolean(adapter.props[ADAPTER_IFACE]["Discovering"]),
},
[],
],
)
@dbus.service.method(ADAPTER_IFACE, in_signature="a{sv}", out_signature="")
def SetDiscoveryFilter(adapter, discovery_filter):
adapter.props[ADAPTER_IFACE]["DiscoveryFilter"] = discovery_filter
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="ss", out_signature="s")
def AddAdapter(self, device_name, system_name):
"""Convenience method to add a Bluetooth adapter
You have to specify a device name which must be a valid part of an object
path, e. g. "hci0", and an arbitrary system name (pretty hostname).
Returns the new object path.
"""
path = "/org/bluez/" + device_name
address_start = int(device_name[-1])
address = (
f"{address_start:02d}:{address_start + 1:02d}:{address_start + 2:02d}:"
f"{address_start + 3:02d}:{address_start + 4:02d}:{address_start + 5:02d}"
)
adapter_properties = {
"UUIDs": dbus.Array(
[
# Reference:
# http://git.kernel.org/cgit/bluetooth/bluez.git/tree/lib/uuid.h
# PNP
"00001200-0000-1000-8000-00805f9b34fb",
# Generic Access Profile
"00001800-0000-1000-8000-00805f9b34fb",
# Generic Attribute Profile
"00001801-0000-1000-8000-00805f9b34fb",
# Audio/Video Remote Control Profile (remote)
"0000110e-0000-1000-8000-00805f9b34fb",
# Audio/Video Remote Control Profile (target)
"0000110c-0000-1000-8000-00805f9b34fb",
],
),
"Discoverable": dbus.Boolean(False),
"Discovering": dbus.Boolean(False),
"Pairable": dbus.Boolean(True),
"Powered": dbus.Boolean(True),
"Address": dbus.String(address),
"AddressType": dbus.String("public"),
"Alias": dbus.String(system_name),
"Modalias": dbus.String("usb:v1D6Bp0245d050A"),
"Name": dbus.String(system_name),
# Reference:
# http://bluetooth-pentest.narod.ru/software/
# bluetooth_class_of_device-service_generator.html
"Class": dbus.UInt32(268), # Computer, Laptop
"DiscoverableTimeout": dbus.UInt32(180),
"PairableTimeout": dbus.UInt32(0),
"Roles": dbus.Array(["central", "peripheral"]),
}
self.AddObject(
path,
ADAPTER_IFACE,
# Properties
adapter_properties,
# Methods
[
("RemoveDevice", "o", "", RemoveDevice),
("StartDiscovery", "", "", StartDiscovery),
("StopDiscovery", "", "", StopDiscovery),
("SetDiscoveryFilter", "a{sv}", "", SetDiscoveryFilter),
],
)
adapter = mockobject.objects[path]
adapter.AddMethods(
MEDIA_IFACE,
[
("RegisterEndpoint", "oa{sv}", "", ""),
("UnregisterEndpoint", "o", "", ""),
],
)
adapter.AddMethods(
NETWORK_SERVER_IFACE,
[
("Register", "ss", "", ""),
("Unregister", "s", "", ""),
],
)
bluez = mockobject.objects["/org/bluez"]
# Advertising Manager
if bluez.enable_advertise_api:
# Example values below from an Intel AX200 adapter
advertising_manager_properties = {
"ActiveInstances": dbus.Byte(0),
"SupportedInstances": dbus.Byte(MAX_ADVERTISEMENT_INSTANCES),
"SupportedIncludes": dbus.Array(["tx-power", "appearance", "local-name", "rssi"]),
"SupportedSecondaryChannels": dbus.Array(["1M", "2M", "Coded"]),
"SupportedCapabilities": dbus.Dictionary(
{
"MaxAdvLen": dbus.Byte(251),
"MaxScnRspLen": dbus.Byte(251),
"MinTxPower": dbus.Int16(-34),
"MaxTxPower": dbus.Int16(7),
},
signature="sv",
),
"SupportedFeatures": dbus.Array(
[
"CanSetTxPower",
"HardwareOffload",
],
),
}
adapter.AddProperties(LE_ADVERTISING_MANAGER_IFACE, advertising_manager_properties)
adapter.AddMethods(
LE_ADVERTISING_MANAGER_IFACE,
[
("RegisterAdvertisement", "oa{sv}", "", RegisterAdvertisement),
("UnregisterAdvertisement", "o", "", UnregisterAdvertisement),
],
)
# Track advertisements per adapter
adapter.advertisements = []
# Advertisement Monitor Manager
if bluez.enable_monitor_api:
advertisement_monitor_manager_properties = {
"SupportedMonitorTypes": dbus.Array(["or_patterns"]),
}
adapter.AddProperties(ADVERTISEMENT_MONITOR_MANAGER_IFACE, advertisement_monitor_manager_properties)
adapter.AddMethods(
ADVERTISEMENT_MONITOR_MANAGER_IFACE,
[
("RegisterMonitor", "o", "", RegisterMonitor),
("UnregisterMonitor", "o", "", UnregisterMonitor),
],
)
# Track advertisement monitors per adapter
adapter.monitors = []
manager = mockobject.objects["/"]
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesAdded",
"oa{sa{sv}}",
[
dbus.ObjectPath(path),
{ADAPTER_IFACE: adapter_properties},
],
)
return path
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="s")
def RemoveAdapter(self, device_name):
"""Convenience method to remove a Bluetooth adapter"""
path = "/org/bluez/" + device_name
# We could remove the devices related to the adapters here, but
# when bluez crashes, the InterfacesRemoved aren't necessarily sent
# devices first, so in effect, our laziness is testing an edge case
# in the clients
self.RemoveObject(path)
manager = mockobject.objects["/"]
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesRemoved",
"oas",
[
dbus.ObjectPath(path),
[ADAPTER_IFACE],
],
)
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="s")
def RemoveAdapterWithDevices(self, device_name):
"""Convenience method to remove a Bluetooth adapter and all
the devices associated to it
"""
adapter_path = "/org/bluez/" + device_name
adapter = mockobject.objects[adapter_path]
manager = mockobject.objects["/"]
to_remove = []
for path in mockobject.objects:
if path.startswith(adapter_path + "/"):
to_remove.append(path)
for path in to_remove:
adapter.RemoveObject(path)
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesRemoved",
"oas",
[
dbus.ObjectPath(path),
[DEVICE_IFACE],
],
)
self.RemoveObject(adapter_path)
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesRemoved",
"oas",
[
dbus.ObjectPath(adapter_path),
[ADAPTER_IFACE],
],
)
@dbus.service.method(DEVICE_IFACE, in_signature="", out_signature="")
def Pair(device):
if device.paired:
raise dbus.exceptions.DBusException("Device already paired", name="org.bluez.Error.AlreadyExists")
device_address = device.props[DEVICE_IFACE]["Address"]
adapter_device_name = Path(device.props[DEVICE_IFACE]["Adapter"]).name
device.PairDevice(adapter_device_name, device_address)
@dbus.service.method(DEVICE_IFACE, in_signature="", out_signature="")
def Connect(device):
if device.connected:
raise dbus.exceptions.DBusException("Already Connected", name="org.bluez.Error.AlreadyConnected")
device.connected = True
device.EmitSignal(
dbus.PROPERTIES_IFACE,
"PropertiesChanged",
"sa{sv}as",
[
DEVICE_IFACE,
{
"Connected": dbus.Boolean(device.connected),
},
[],
],
)
@dbus.service.method(DEVICE_IFACE, in_signature="", out_signature="")
def Disconnect(device):
if not device.connected:
raise dbus.exceptions.DBusException("Not Connected", name="org.bluez.Error.NotConnected")
device.connected = False
device.EmitSignal(
dbus.PROPERTIES_IFACE,
"PropertiesChanged",
"sa{sv}as",
[
DEVICE_IFACE,
{
"Connected": dbus.Boolean(device.connected),
},
[],
],
)
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="sss", out_signature="s")
def AddDevice(self, adapter_device_name, device_address, alias):
"""Convenience method to add a Bluetooth device
You have to specify a device address which must be a valid Bluetooth
address (e.g. 'AA:BB:CC:DD:EE:FF'). The alias is the human-readable name
for the device (e.g. as set on the device itself), and the adapter device
name is the device_name passed to AddAdapter.
This will create a new, unpaired and unconnected device with some default properties
like MOCK_PHONE_CLASS "Class" and a static "Modalias". Especially when working with more
than one device, you may want to change these after creation.
Returns the new object path.
"""
device_name = "dev_" + device_address.replace(":", "_").upper()
adapter_path = "/org/bluez/" + adapter_device_name
path = adapter_path + "/" + device_name
if adapter_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Adapter {adapter_device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchAdapter"
)
properties = {
"Address": dbus.String(device_address),
"AddressType": dbus.String("public"),
"Name": dbus.String(alias),
"Icon": dbus.String("phone"),
"Class": dbus.UInt32(MOCK_PHONE_CLASS),
"Appearance": dbus.UInt16(0),
"UUIDs": dbus.Array([], signature="s"),
"Paired": dbus.Boolean(False),
"Connected": dbus.Boolean(False),
"Trusted": dbus.Boolean(False),
"Blocked": dbus.Boolean(False),
"WakeAllowed": dbus.Boolean(False),
"Alias": dbus.String(alias),
"Adapter": dbus.ObjectPath(adapter_path),
"LegacyPairing": dbus.Boolean(False),
"Modalias": dbus.String("bluetooth:v000Fp1200d1436"),
"RSSI": dbus.Int16(-79), # arbitrary
"TxPower": dbus.Int16(0),
"ManufacturerData": dbus.Array([], signature="a{qv}"),
"ServiceData": dbus.Array([], signature="a{sv}"),
"ServicesResolved": dbus.Boolean(False),
"AdvertisingFlags": dbus.Array([], signature="ay"),
"AdvertisingData": dbus.Array([], signature="a{yv}"),
}
self.AddObject(
path,
DEVICE_IFACE,
# Properties
properties,
# Methods
[
("CancelPairing", "", "", ""),
("Connect", "", "", Connect),
("ConnectProfile", "s", "", ""),
("Disconnect", "", "", Disconnect),
("DisconnectProfile", "s", "", ""),
("Pair", "", "", Pair),
],
)
device = mockobject.objects[path]
device.paired = False
device.connected = False
manager = mockobject.objects["/"]
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesAdded",
"oa{sa{sv}}",
[
dbus.ObjectPath(path),
{DEVICE_IFACE: properties},
],
)
return path
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="ss", out_signature="")
def PairDevice(_self, adapter_device_name, device_address):
"""Convenience method to mark an existing device as paired.
You have to specify a device address which must be a valid Bluetooth
address (e.g. 'AA:BB:CC:DD:EE:FF'). The adapter device name is the
device_name passed to AddAdapter.
This unblocks the device if it was blocked.
If the specified adapter or device doesn't exist, a NoSuchAdapter or
NoSuchDevice error will be returned on the bus.
Returns nothing.
"""
device_name = "dev_" + device_address.replace(":", "_").upper()
adapter_path = "/org/bluez/" + adapter_device_name
device_path = adapter_path + "/" + device_name
if adapter_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Adapter {adapter_device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchAdapter"
)
if device_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Device {device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchDevice"
)
device = mockobject.objects[device_path]
device.paired = True
# Based off pairing with an Android phone.
uuids = [
"00001105-0000-1000-8000-00805f9b34fb",
"0000110a-0000-1000-8000-00805f9b34fb",
"0000110c-0000-1000-8000-00805f9b34fb",
"00001112-0000-1000-8000-00805f9b34fb",
"00001115-0000-1000-8000-00805f9b34fb",
"00001116-0000-1000-8000-00805f9b34fb",
"0000111f-0000-1000-8000-00805f9b34fb",
"0000112f-0000-1000-8000-00805f9b34fb",
"00001200-0000-1000-8000-00805f9b34fb",
]
device.UpdateProperties(
DEVICE_IFACE,
{
"UUIDs": dbus.Array(uuids),
"Paired": dbus.Boolean(True),
"LegacyPairing": dbus.Boolean(True),
"Blocked": dbus.Boolean(False),
},
)
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="ss", out_signature="")
def BlockDevice(_self, adapter_device_name, device_address):
"""Convenience method to mark an existing device as blocked.
You have to specify a device address which must be a valid Bluetooth
address (e.g. 'AA:BB:CC:DD:EE:FF'). The adapter device name is the
device_name passed to AddAdapter.
This disconnects the device if it was connected.
If the specified adapter or device doesn't exist, a NoSuchAdapter or
NoSuchDevice error will be returned on the bus.
Returns nothing.
"""
device_name = "dev_" + device_address.replace(":", "_").upper()
adapter_path = "/org/bluez/" + adapter_device_name
device_path = adapter_path + "/" + device_name
if adapter_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Adapter {adapter_device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchAdapter"
)
if device_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Device {device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchDevice"
)
device = mockobject.objects[device_path]
device.props[DEVICE_IFACE]["Blocked"] = dbus.Boolean(True)
device.props[DEVICE_IFACE]["Connected"] = dbus.Boolean(False)
device.EmitSignal(
dbus.PROPERTIES_IFACE,
"PropertiesChanged",
"sa{sv}as",
[
DEVICE_IFACE,
{
"Blocked": dbus.Boolean(True),
"Connected": dbus.Boolean(False),
},
[],
],
)
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="ss", out_signature="")
def ConnectDevice(_self, adapter_device_name, device_address):
"""Convenience method to mark an existing device as connected.
You have to specify a device address which must be a valid Bluetooth
address (e.g. 'AA:BB:CC:DD:EE:FF'). The adapter device name is the
device_name passed to AddAdapter.
This unblocks the device if it was blocked.
If the specified adapter or device doesn't exist, a NoSuchAdapter or
NoSuchDevice error will be returned on the bus.
Returns nothing.
"""
device_name = "dev_" + device_address.replace(":", "_").upper()
adapter_path = "/org/bluez/" + adapter_device_name
device_path = adapter_path + "/" + device_name
if adapter_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Adapter {adapter_device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchAdapter"
)
if device_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Device {device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchDevice"
)
device = mockobject.objects[device_path]
device.props[DEVICE_IFACE]["Blocked"] = dbus.Boolean(False)
device.props[DEVICE_IFACE]["Connected"] = dbus.Boolean(True)
device.EmitSignal(
dbus.PROPERTIES_IFACE,
"PropertiesChanged",
"sa{sv}as",
[
DEVICE_IFACE,
{
"Blocked": dbus.Boolean(False),
"Connected": dbus.Boolean(True),
},
[],
],
)
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="ss", out_signature="")
def DisconnectDevice(_self, adapter_device_name, device_address):
"""Convenience method to mark an existing device as disconnected.
You have to specify a device address which must be a valid Bluetooth
address (e.g. 'AA:BB:CC:DD:EE:FF'). The adapter device name is the
device_name passed to AddAdapter.
This does not change the device's blocked status.
If the specified adapter or device doesn't exist, a NoSuchAdapter or
NoSuchDevice error will be returned on the bus.
Returns nothing.
"""
device_name = "dev_" + device_address.replace(":", "_").upper()
adapter_path = "/org/bluez/" + adapter_device_name
device_path = adapter_path + "/" + device_name
if adapter_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Adapter {adapter_device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchAdapter"
)
if device_path not in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Device {device_name} does not exist.", name=BLUEZ_MOCK_IFACE + ".NoSuchDevice"
)
device = mockobject.objects[device_path]
device.props[DEVICE_IFACE]["Connected"] = dbus.Boolean(False)
device.EmitSignal(
dbus.PROPERTIES_IFACE,
"PropertiesChanged",
"sa{sv}as",
[
DEVICE_IFACE,
{
"Connected": dbus.Boolean(False),
},
[],
],
)
def RegisterAdvertisement(manager, adv_path, options): # pylint: disable=unused-argument
if adv_path in manager.advertisements:
raise dbus.exceptions.DBusException("Already registered: " + adv_path, name="org.bluez.Error.AlreadyExists")
if len(manager.advertisements) >= MAX_ADVERTISEMENT_INSTANCES:
raise dbus.exceptions.DBusException(
f"Maximum number of advertisements reached: {MAX_ADVERTISEMENT_INSTANCES}",
name="org.bluez.Error.NotPermitted",
)
manager.advertisements.append(adv_path)
manager.UpdateProperties(
LE_ADVERTISING_MANAGER_IFACE,
{
"ActiveInstances": dbus.Byte(len(manager.advertisements)),
"SupportedInstances": dbus.Byte(MAX_ADVERTISEMENT_INSTANCES - len(manager.advertisements)),
},
)
def UnregisterAdvertisement(manager, adv_path):
try:
manager.advertisements.remove(adv_path)
except ValueError:
raise dbus.exceptions.DBusException(
"Unknown advertisement: " + adv_path, name="org.bluez.Error.DoesNotExist"
) from None
manager.UpdateProperties(
LE_ADVERTISING_MANAGER_IFACE,
{
"ActiveInstances": dbus.Byte(len(manager.advertisements)),
"SupportedInstances": dbus.Byte(MAX_ADVERTISEMENT_INSTANCES - len(manager.advertisements)),
},
)
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="s", out_signature="s")
def AddAdvertisement(self, adv_name):
"""Convenience method to add an Advertisement object
Creates a simple broadcast advertisement with some manufacturer data.
Returns the new object path.
"""
path = "/org/dbusmock/bluez/advertisement/" + adv_name
adv_properties = {
"Type": dbus.String("broadcast"),
"ManufacturerData": dbus.Dictionary(
# 0xFFFF is the Bluetooth Company Identifier reserved for internal use and testing.
{dbus.UInt16(0xFFFF): dbus.Array([0x00, 0x01])},
signature="qv",
),
"Includes": dbus.Array(["local-name"]),
}
self.AddObject(
path,
LE_ADVERTISEMENT_IFACE,
adv_properties,
[
("Release", "", "", ""),
],
)
return path
@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="s", out_signature="s")
def AddMonitor(self, monitor_name):
"""Convenience method to add an Advertisement Monitor
Returns the new object path.
"""
path = "/org/dbusmock/bluez/monitor/" + monitor_name
monitor_properties = {
"Type": dbus.String("or_patterns"),
# Example pattern that could be used to scan for an advertisement created by AddAdvertisement()
"Patterns": dbus.Struct(
(
# Start position: 0
dbus.Byte(0),
# AD data type: Manufacturer data
dbus.Byte(0xFF),
# Vaue of the pattern: 0xFFFF (company identifier), followed by 0x01
dbus.Array(
[
dbus.UInt16(0xFFFF),
dbus.Byte(0x01),
]
),
),
signature="yyay",
),
}
self.AddObject(
path,
ADVERTISEMENT_MONITOR_IFACE,
monitor_properties,
[
("Release", "", "", ""),
("Activate", "", "", ""),
("DeviceFound", "o", "", ""),
("DeviceLost", "o", "", ""),
],
)
return path
def RegisterMonitor(manager, monitor_path):
if monitor_path in manager.monitors:
raise dbus.exceptions.DBusException(
"Already registered: " + monitor_path, name="org.bluez.Error.AlreadyExists"
)
manager.monitors.append(monitor_path)
def UnregisterMonitor(manager, monitor_path):
try:
manager.monitors.remove(monitor_path)
except ValueError:
raise dbus.exceptions.DBusException(
"Unknown monitor: " + monitor_path, name="org.bluez.Error.DoesNotExist"
) from None
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/gnome_screensaver.py 0000644 0001751 0001751 00000002500 15071377476 024362 0 ustar 00runner runner """gnome-shell screensaver mock template
This creates the expected methods and properties of the
org.gnome.ScreenSaver object.
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Bastien Nocera"
__copyright__ = """
(c) 2013 Red Hat Inc.
(c) 2017 - 2022 Martin Pitt
"""
BUS_NAME = "org.gnome.ScreenSaver"
MAIN_OBJ = "/org/gnome/ScreenSaver"
MAIN_IFACE = "org.gnome.ScreenSaver"
SYSTEM_BUS = False
def load(mock, _parameters):
mock.AddMethods(
MAIN_IFACE,
[
("GetActive", "", "b", "ret = self.is_active"),
("GetActiveTime", "", "u", "ret = 1"),
(
"SetActive",
"b",
"",
'self.is_active = args[0]; self.EmitSignal("", "ActiveChanged", "b", [self.is_active])',
),
("Lock", "", "", "time.sleep(1); self.SetActive(True)"),
("ShowMessage", "sss", "", ""),
("SimulateUserActivity", "", "", ""),
],
)
# default state
mock.is_active = False
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/gsd_rfkill.py 0000644 0001751 0001751 00000004116 15071377476 023002 0 ustar 00runner runner """gsd-rfkill mock template
This creates the expected properties of the GNOME Settings Daemon's
rfkill object. You can specify any property such as AirplaneMode in
"parameters".
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Guido Günther"
__copyright__ = "2024 The Phosh Developers"
import dbus
from dbusmock import MOCK_IFACE
SYSTEM_BUS = False
BUS_NAME = "org.gnome.SettingsDaemon.Rfkill"
MAIN_OBJ = "/org/gnome/SettingsDaemon/Rfkill"
MAIN_IFACE = "org.gnome.SettingsDaemon.Rfkill"
def load(mock, parameters):
props = dbus.Dictionary(
{
"AirplaneMode": parameters.get("AirplaneMode", False),
"BluetoothAirplaneMode": parameters.get("BluetoothAirplaneMode", False),
"BluetoothHardwareAirplaneMode": parameters.get("BluetoothHardwareAirplaneMode", False),
"BluetoothHasAirplaneMode": parameters.get("BluetoothHasAirplanemode", True),
"HardwareAirplaneMode": parameters.get("HardwareAirplaneMode", False),
"HasAirplaneMode": parameters.get("HasAirplaneMode", True),
"ShouldShowAirplaneMode": parameters.get("ShouldShowAirplaneMode", True),
"WwanAirplaneMode": parameters.get("WwanAirplaneMode", False),
"WwanHardwareAirplaneMode": parameters.get("WwanHardwareAirplaneMode", False),
"WwanHasAirplaneMode": parameters.get("WwanHasAirplaneMode", True),
},
signature="sv",
)
mock.AddProperties(MAIN_IFACE, props)
@dbus.service.method(MOCK_IFACE, in_signature="b", out_signature="b")
def SetAirplaneMode(self, mode):
"""
Convenience method to toggle airplane mode
"""
self.props[MAIN_IFACE]["AirplaneMode"] = mode
self.props[MAIN_IFACE]["BluetoothAirplaneMode"] = mode
self.props[MAIN_IFACE]["WwanAirplaneMode"] = mode
return mode
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/logind.py 0000644 0001751 0001751 00000024775 15071377476 022153 0 ustar 00runner runner """systemd logind mock template
This creates the expected methods and properties of the main
org.freedesktop.login1.Manager object. You can specify D-Bus property values
like "CanSuspend" or the return value of Inhibit() in "parameters".
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import os
import dbus
from gi.repository import GLib
from dbusmock import MOCK_IFACE, mockobject
BUS_NAME = "org.freedesktop.login1"
MAIN_OBJ = "/org/freedesktop/login1"
MAIN_IFACE = "org.freedesktop.login1.Manager"
SYSTEM_BUS = True
def load(mock, parameters):
mock.AddMethods(
MAIN_IFACE,
[
("PowerOff", "b", "", ""),
("Reboot", "b", "", ""),
("Suspend", "b", "", ""),
("Hibernate", "b", "", ""),
("HybridSleep", "b", "", ""),
("SuspendThenHibernate", "b", "", ""),
("CanPowerOff", "", "s", f'ret = "{parameters.get("CanPowerOff", "yes")}"'),
("CanReboot", "", "s", f'ret = "{parameters.get("CanReboot", "yes")}"'),
("CanSuspend", "", "s", f'ret = "{parameters.get("CanSuspend", "yes")}"'),
("CanHibernate", "", "s", f'ret = "{parameters.get("CanHibernate", "yes")}"'),
("CanHybridSleep", "", "s", f'ret = "{parameters.get("CanHybridSleep", "yes")}"'),
("CanSuspendThenHibernate", "", "s", f'ret = "{parameters.get("CanSuspendThenHibernate", "yes")}"'),
("GetSession", "s", "o", 'ret = "/org/freedesktop/login1/session/" + args[0]'),
("ActivateSession", "s", "", ""),
("ActivateSessionOnSeat", "ss", "", ""),
("KillSession", "sss", "", ""),
("LockSession", "s", "", ""),
("LockSessions", "", "", ""),
("ReleaseSession", "s", "", ""),
("TerminateSession", "s", "", ""),
("UnlockSession", "s", "", ""),
("UnlockSessions", "", "", ""),
("GetSeat", "s", "o", 'ret = "/org/freedesktop/login1/seat/" + args[0]'),
("ListSeats", "", "a(so)", 'ret = [(k.split("/")[-1], k) for k in objects.keys() if "/seat/" in k]'),
("TerminateSeat", "s", "", ""),
("GetUser", "u", "o", 'ret = "/org/freedesktop/login1/user/" + args[0]'),
("KillUser", "us", "", ""),
("TerminateUser", "u", "", ""),
],
)
mock.AddProperties(
MAIN_IFACE,
dbus.Dictionary(
{
"IdleHint": parameters.get("IdleHint", False),
"IdleAction": parameters.get("IdleAction", "ignore"),
"IdleSinceHint": dbus.UInt64(parameters.get("IdleSinceHint", 0)),
"IdleSinceHintMonotonic": dbus.UInt64(parameters.get("IdleSinceHintMonotonic", 0)),
"IdleActionUSec": dbus.UInt64(parameters.get("IdleActionUSec", 1)),
"PreparingForShutdown": parameters.get("PreparingForShutdown", False),
"PreparingForSleep": parameters.get("PreparingForSleep", False),
},
signature="sv",
),
)
#
# logind methods which are too big for squeezing into AddMethod()
#
@dbus.service.method(MAIN_IFACE, in_signature="", out_signature="a(uso)")
def ListUsers(_):
users = []
for k, obj in mockobject.objects.items():
if "/user/" in k:
uid = dbus.UInt32(int(k.split("/")[-1]))
users.append((uid, obj.Get("org.freedesktop.login1.User", "Name"), k))
return users
@dbus.service.method(MAIN_IFACE, in_signature="", out_signature="a(susso)")
def ListSessions(_):
sessions = []
for k, obj in mockobject.objects.items():
if "/session/" in k:
session_id = k.split("/")[-1]
uid = obj.Get("org.freedesktop.login1.Session", "User")[0]
username = obj.Get("org.freedesktop.login1.Session", "Name")
seat = obj.Get("org.freedesktop.login1.Session", "Seat")[0]
sessions.append((session_id, uid, username, seat, k))
return sessions
@dbus.service.method(MAIN_IFACE, in_signature="ssss", out_signature="h")
def Inhibit(_, what, who, why, mode):
if not hasattr(mockobject, "inhibitors"):
mockobject.inhibitors = []
fd_r, fd_w = os.pipe()
inhibitor = (what, who, why, mode, 1000, 123456)
mockobject.inhibitors.append(inhibitor)
def inhibitor_dropped(fd, cond):
# pylint: disable=unused-argument
os.close(fd)
mockobject.inhibitors.remove(inhibitor)
return False
GLib.unix_fd_add_full(GLib.PRIORITY_HIGH, fd_r, GLib.IO_HUP, inhibitor_dropped)
GLib.idle_add(os.close, fd_w)
return fd_w
@dbus.service.method(MAIN_IFACE, in_signature="", out_signature="a(ssssuu)")
def ListInhibitors(_):
if not hasattr(mockobject, "inhibitors"):
mockobject.inhibitors = []
return mockobject.inhibitors
#
# Convenience methods on the mock
#
@dbus.service.method(MOCK_IFACE, in_signature="s", out_signature="s")
def AddSeat(self, seat):
"""Convenience method to add a seat.
Return the object path of the new seat.
"""
seat_path = "/org/freedesktop/login1/seat/" + seat
if seat_path in mockobject.objects:
raise dbus.exceptions.DBusException(f"Seat {seat} already exists", name=MOCK_IFACE + ".SeatExists")
self.AddObject(
seat_path,
"org.freedesktop.login1.Seat",
{
"Sessions": dbus.Array([], signature="(so)"),
"CanGraphical": False,
"CanMultiSession": True,
"CanTTY": False,
"IdleHint": False,
"ActiveSession": ("", dbus.ObjectPath("/")),
"Id": seat,
"IdleSinceHint": dbus.UInt64(0),
"IdleSinceHintMonotonic": dbus.UInt64(0),
},
[("ActivateSession", "s", "", ""), ("Terminate", "", "", "")],
)
return seat_path
@dbus.service.method(MOCK_IFACE, in_signature="usb", out_signature="s")
def AddUser(self, uid, username, active):
"""Convenience method to add a user.
Return the object path of the new user.
"""
user_path = f"/org/freedesktop/login1/user/{uid}"
if user_path in mockobject.objects:
raise dbus.exceptions.DBusException(f"User {uid} already exists", name=MOCK_IFACE + ".UserExists")
self.AddObject(
user_path,
"org.freedesktop.login1.User",
{
"DefaultControlGroup": "systemd:/user/" + username,
"Display": ("", dbus.ObjectPath("/")),
"GID": dbus.UInt32(uid),
"IdleHint": False,
"IdleSinceHint": dbus.UInt64(0),
"IdleSinceHintMonotonic": dbus.UInt64(0),
"Linger": False,
"Name": username,
"RuntimePath": f"/run/user/{uid}",
"Service": "",
"Sessions": dbus.Array([], signature="(so)"),
"Slice": f"user-{uid}.slice",
"State": "active" if active else "online",
"Timestamp": dbus.UInt64(42),
"TimestampMonotonic": dbus.UInt64(42),
"UID": dbus.UInt32(uid),
},
[
("Kill", "s", "", ""),
("Terminate", "", "", ""),
],
)
return user_path
@dbus.service.method(MOCK_IFACE, in_signature="ssusb", out_signature="s")
def AddSession(self, session_id, seat, uid, username, active):
"""Convenience method to add a session.
If the given seat and/or user do not exit, they will be created.
Return the object path of the new session.
"""
seat_path = dbus.ObjectPath(f"/org/freedesktop/login1/seat/{seat}")
if seat_path not in mockobject.objects:
self.AddSeat(seat)
user_path = dbus.ObjectPath(f"/org/freedesktop/login1/user/{uid}")
if user_path not in mockobject.objects:
self.AddUser(uid, username, active)
session_path = dbus.ObjectPath(f"/org/freedesktop/login1/session/{session_id}")
if session_path in mockobject.objects:
raise dbus.exceptions.DBusException(
f"Session {session_id} already exists", name=MOCK_IFACE + ".SessionExists"
)
self.AddObject(
session_path,
"org.freedesktop.login1.Session",
{
"Controllers": dbus.Array([], signature="s"),
"ResetControllers": dbus.Array([], signature="s"),
"Active": active,
"IdleHint": False,
"LockedHint": False,
"KillProcesses": False,
"Remote": False,
"Class": "user",
"DefaultControlGroup": f"systemd:/user/{username}/{session_id}",
"Display": os.getenv("DISPLAY", ""),
"Id": session_id,
"Name": username,
"RemoteHost": "",
"RemoteUser": "",
"Service": "dbusmock",
"State": "active" if active else "online",
"TTY": "",
"Type": "test",
"Seat": (seat, seat_path),
"User": (dbus.UInt32(uid), user_path),
"Audit": dbus.UInt32(0),
"Leader": dbus.UInt32(1),
"VTNr": dbus.UInt32(1),
"IdleSinceHint": dbus.UInt64(0),
"IdleSinceHintMonotonic": dbus.UInt64(0),
"Timestamp": dbus.UInt64(42),
"TimestampMonotonic": dbus.UInt64(42),
},
[
("Activate", "", "", ""),
("Kill", "ss", "", ""),
("Lock", "", "", 'self.EmitSignal("", "Lock", "", [])'),
("SetIdleHint", "b", "", ""),
("SetLockedHint", "b", "", 'self.UpdateProperties("", {"LockedHint": args[0]})'),
("Terminate", "", "", ""),
("Unlock", "", "", 'self.EmitSignal("", "Unlock", "", [])'),
],
)
# add session to seat
obj_seat = mockobject.objects[seat_path]
cur_sessions = obj_seat.Get("org.freedesktop.login1.Seat", "Sessions")
cur_sessions.append((session_id, session_path))
obj_seat.Set("org.freedesktop.login1.Seat", "Sessions", cur_sessions)
obj_seat.Set("org.freedesktop.login1.Seat", "ActiveSession", (session_id, session_path))
# add session to user
obj_user = mockobject.objects[user_path]
cur_sessions = obj_user.Get("org.freedesktop.login1.User", "Sessions")
cur_sessions.append((session_id, session_path))
obj_user.Set("org.freedesktop.login1.User", "Sessions", cur_sessions)
return session_path
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/low_memory_monitor.py 0000644 0001751 0001751 00000002134 15071377476 024620 0 ustar 00runner runner """low-memory-monitor mock template
This creates the expected methods and properties of the main
org.freedesktop.LowMemoryMonitor object.
This provides only the 2.0 D-Bus API of low-memory-monitor.
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Bastien Nocera"
__copyright__ = """
(c) 2019, Red Hat Inc.
(c) 2017 - 2022 Martin Pitt
"""
import dbus
from dbusmock import MOCK_IFACE
BUS_NAME = "org.freedesktop.LowMemoryMonitor"
MAIN_OBJ = "/org/freedesktop/LowMemoryMonitor"
MAIN_IFACE = "org.freedesktop.LowMemoryMonitor"
SYSTEM_BUS = True
def load(mock, _parameters):
# Loaded!
mock.loaded = True
@dbus.service.method(MOCK_IFACE, in_signature="y", out_signature="")
def EmitWarning(self, state):
self.EmitSignal(MAIN_IFACE, "LowMemoryWarning", "y", [dbus.Byte(state)])
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/modemmanager.py 0000644 0001751 0001751 00000021602 15071377476 023315 0 ustar 00runner runner """ModemManager mock template
This creates the expected methods and properties of the main
ModemManager object, but no devices. You can specify any property
such as DaemonVersion in "parameters".
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Guido Günther"
__copyright__ = "2024 The Phosh Developers"
import dbus
from dbusmock import MOCK_IFACE, OBJECT_MANAGER_IFACE, mockobject
BUS_NAME = "org.freedesktop.ModemManager1"
MAIN_OBJ = "/org/freedesktop/ModemManager1"
MAIN_IFACE = "org.freedesktop.ModemManager1"
SYSTEM_BUS = True
IS_OBJECT_MANAGER = False
MODEM_IFACE = "org.freedesktop.ModemManager1.Modem"
MODEM_3GPP_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp"
MODEM_CELL_BROADCAST_IFACE = "org.freedesktop.ModemManager1.Modem.CellBroadcast"
MODEM_VOICE_IFACE = "org.freedesktop.ModemManager1.Modem.Voice"
SIM_IFACE = "org.freedesktop.ModemManager1.Sim"
CBM_IFACE = "org.freedesktop.ModemManager1.Cbm"
SIMPLE_MODEM_PATH = "/org/freedesktop/ModemManager1/Modems/8"
class MMModemMode:
"""
See
https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemMode
"""
MODE_NONE = 0
MODE_CS = 1 << 0
MODE_2G = 1 << 1
MODE_3G = 1 << 2
MODE_4G = 1 << 3
MODE_5G = 1 << 4
class MMModemState:
"""
See
https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemState
"""
STATE_FAILED = -1
STATE_UNKNOWN = 0
STATE_INITIALIZING = 1
STATE_LOCKED = 2
STATE_DISABLED = 3
STATE_DISABLING = 4
STATE_ENABLING = 5
STATE_ENABLED = 6
STATE_SEARCHING = 7
STATE_REGISTERED = 8
STATE_DISCONNECTING = 9
STATE_CONNECTING = 10
STATE_CONNECTED = 11
class MMModemPowerState:
"""
See
https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemPowerState
"""
POWER_STATE_UNKNOWN = 0
POWER_STATE_OFF = 1
POWER_STATE_LOW = 2
POWER_STATE_ON = 3
class MMModemAccesssTechnology:
"""
See
https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemAccessTechnology
"""
ACCESS_TECHNOLOGY_UNKNOWN = 0
ACCESS_TECHNOLOGY_POTS = 1 << 0
ACCESS_TECHNOLOGY_GSM = 1 << 1
ACCESS_TECHNOLOGY_GSM_COMPACT = 1 << 2
ACCESS_TECHNOLOGY_GPRS = 1 << 3
ACCESS_TECHNOLOGY_EDGE = 1 << 4
ACCESS_TECHNOLOGY_UMTS = 1 << 5
ACCESS_TECHNOLOGY_HSDPA = 1 << 6
ACCESS_TECHNOLOGY_HSUPA = 1 << 7
ACCESS_TECHNOLOGY_HSPA = 1 << 8
ACCESS_TECHNOLOGY_HSPA_PLUS = 1 << 9
ACCESS_TECHNOLOGY_1XRTT = 1 << 10
ACCESS_TECHNOLOGY_EVDO0 = 1 << 11
ACCESS_TECHNOLOGY_EVDOA = 1 << 12
ACCESS_TECHNOLOGY_EVDOB = 1 << 13
ACCESS_TECHNOLOGY_LTE = 1 << 14
ACCESS_TECHNOLOGY_5GNR = 1 << 15
ACCESS_TECHNOLOGY_LTE_CAT_M = 1 << 16
ACCESS_TECHNOLOGY_LTE_NB_IOT = 1 << 17
def load(mock, parameters):
methods = [
("ScanDevices", "", "", ""),
]
props = dbus.Dictionary(
{
"Version": parameters.get("DaemonVersion", "1.22"),
},
signature="sv",
)
mock.AddMethods(MAIN_IFACE, methods)
mock.AddProperties(MAIN_IFACE, props)
cond = "k != '/'" if mock.path == "/" else f"k.startswith('{mock.path}/Modems/')"
code = f"ret = {{dbus.ObjectPath(k): objects[k].props for k in objects.keys() if {cond} }}"
mock.AddMethod(OBJECT_MANAGER_IFACE, "GetManagedObjects", "", "a{oa{sa{sv}}}", code)
def listCbm(_):
paths = []
for path in mockobject.objects:
if path.startswith("/org/freedesktop/ModemManager1/Cbm/"):
paths.append(dbus.ObjectPath(path))
return paths
def deleteCbm(self, cbm_path):
obj = mockobject.objects.get(cbm_path)
if obj is None:
return
modem_obj = mockobject.objects[SIMPLE_MODEM_PATH]
self.RemoveObject(cbm_path)
modem_obj.EmitSignal(
MODEM_CELL_BROADCAST_IFACE,
"Deleted",
"o",
[
cbm_path,
],
)
def setChannels(_self, channels):
modem_obj = mockobject.objects[SIMPLE_MODEM_PATH]
modem_obj.UpdateProperties(
MODEM_CELL_BROADCAST_IFACE,
{
"Channels": dbus.Array(channels),
},
)
@dbus.service.method(MOCK_IFACE, in_signature="", out_signature="ss")
def AddSimpleModem(self):
"""Convenience method to add a simple Modem object
Please note that this does not set any global properties.
Returns the new object path.
"""
modem_path = SIMPLE_MODEM_PATH
sim_path = "/org/freedesktop/ModemManager1/SIM/2"
manager = mockobject.objects[MAIN_OBJ]
modem_props = {
"State": dbus.Int32(MMModemState.STATE_ENABLED),
"Model": dbus.String("E1750"),
"Revision": dbus.String("11.126.08.01.00"),
"AccessTechnologies": dbus.UInt32(MMModemAccesssTechnology.ACCESS_TECHNOLOGY_LTE),
"PowerState": dbus.UInt32(MMModemPowerState.POWER_STATE_ON),
"UnlockRequired": dbus.UInt32(0),
"UnlockRetries": dbus.Dictionary([], signature="uu"),
"CurrentModes": dbus.Struct(
(dbus.UInt32(MMModemMode.MODE_4G), dbus.UInt32(MMModemMode.MODE_4G)), signature="(uu)"
),
"SignalQuality": dbus.Struct(
(dbus.UInt32(70), dbus.Boolean(True)),
),
"Sim": dbus.ObjectPath(sim_path),
"SupportedModes": [
(dbus.UInt32(MMModemMode.MODE_4G), dbus.UInt32(MMModemMode.MODE_4G)),
(dbus.UInt32(MMModemMode.MODE_3G | MMModemMode.MODE_2G), dbus.UInt32(MMModemMode.MODE_3G)),
],
"SupportedBands": [dbus.UInt32(0)],
}
self.AddObject(modem_path, MODEM_IFACE, modem_props, [])
modem_3gpp_props = {
"Imei": dbus.String("doesnotmatter"),
"OperatorName": dbus.String("TheOperator"),
"OperatorCode": dbus.String("00101"),
"Pco": dbus.Array([], signature="(ubay)"),
}
modem = mockobject.objects[modem_path]
modem.AddProperties(MODEM_3GPP_IFACE, modem_3gpp_props)
modem_cell_broadcast_props = {
"Channels": dbus.Array([], signature="(uu)"),
"CellBroadcasts": dbus.Array([], signature="o"),
}
modem_cell_broadcast_methods = [
("List", "", "ao", listCbm),
("Delete", "o", "", deleteCbm),
("SetChannels", "a(uu)", "", setChannels),
]
modem.AddProperties(MODEM_CELL_BROADCAST_IFACE, modem_cell_broadcast_props)
modem.AddMethods(MODEM_CELL_BROADCAST_IFACE, modem_cell_broadcast_methods)
modem_voice_props = {
"Calls": dbus.Array([], signature="o"),
"EmergencyOnly": False,
}
modem.call_waiting = False
modem_voice_methods = [
("CallWaitingQuery", "", "b", "ret = self.call_waiting"),
("CallWaitingSetup", "b", "", "self.call_waiting = args[0]"),
]
modem.AddProperties(MODEM_VOICE_IFACE, modem_voice_props)
modem.AddMethods(MODEM_VOICE_IFACE, modem_voice_methods)
manager.EmitSignal(
OBJECT_MANAGER_IFACE,
"InterfacesAdded",
"oa{sa{sv}}",
[
dbus.ObjectPath(modem_path),
{
MODEM_IFACE: modem_props,
MODEM_3GPP_IFACE: modem_3gpp_props,
MODEM_CELL_BROADCAST_IFACE: modem_cell_broadcast_props,
MODEM_VOICE_IFACE: modem_voice_props,
},
],
)
sim_props = {
"Active": dbus.Boolean(True),
"Imsi": dbus.String("doesnotmatter"),
"PreferredNetworks": dbus.Array([], signature="(su)"),
}
self.AddObject(sim_path, SIM_IFACE, sim_props, [])
return (modem_path, sim_path)
@dbus.service.method(MOCK_IFACE, in_signature="uus", out_signature="s")
def AddCbm(self, state, channel, text):
"""Convenience method to add a cell broadcast message
Returns the new object path.
"""
n = 1
while mockobject.objects.get(f"/org/freedesktop/ModemManager1/Cbm/{n}") is not None:
n += 1
cbm_path = f"/org/freedesktop/ModemManager1/Cbm/{n}"
cbm_props = {
"State": dbus.UInt32(state),
"Channel": dbus.UInt32(channel),
"Text": dbus.String(text),
"MessageCode": dbus.UInt32(0),
"Update": dbus.UInt32(0),
}
self.AddObject(cbm_path, CBM_IFACE, cbm_props, [])
modem_obj = mockobject.objects[SIMPLE_MODEM_PATH]
paths = listCbm(self)
modem_obj.UpdateProperties(
MODEM_CELL_BROADCAST_IFACE,
{
"CellBroadcasts": dbus.Array(paths),
},
)
modem_obj.EmitSignal(
MODEM_CELL_BROADCAST_IFACE,
"Added",
"o",
[
dbus.ObjectPath(cbm_path),
],
)
return cbm_path
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/networkmanager.py 0000644 0001751 0001751 00000103065 15071377476 023711 0 ustar 00runner runner """NetworkManager mock template
This creates the expected methods and properties of the main
org.freedesktop.NetworkManager object, but no devices. You can specify any
property such as 'NetworkingEnabled', or 'WirelessEnabled' etc. in
"parameters".
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Iftikhar Ahmad"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import binascii
import uuid
import dbus
import dbusmock
from dbusmock import MOCK_IFACE
BUS_NAME = "org.freedesktop.NetworkManager"
MAIN_OBJ = "/org/freedesktop"
MANAGER_IFACE = "org.freedesktop.NetworkManager"
MANAGER_OBJ = "/org/freedesktop/NetworkManager"
SETTINGS_OBJ = "/org/freedesktop/NetworkManager/Settings"
SETTINGS_IFACE = "org.freedesktop.NetworkManager.Settings"
DEVICE_IFACE = "org.freedesktop.NetworkManager.Device"
WIRELESS_DEVICE_IFACE = "org.freedesktop.NetworkManager.Device.Wireless"
ACCESS_POINT_IFACE = "org.freedesktop.NetworkManager.AccessPoint"
CSETTINGS_IFACE = "org.freedesktop.NetworkManager.Settings.Connection"
ACTIVE_CONNECTION_IFACE = "org.freedesktop.NetworkManager.Connection.Active"
SYSTEM_BUS = True
IS_OBJECT_MANAGER = True
# these really want to be dataclasses, but need to drop support for Python 3.6 for that
class NMState:
"""Global state
As per https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMState
"""
NM_STATE_UNKNOWN = 0
NM_STATE_ASLEEP = 10
NM_STATE_DISCONNECTED = 20
NM_STATE_DISCONNECTING = 30
NM_STATE_CONNECTING = 40
NM_STATE_CONNECTED_LOCAL = 50
NM_STATE_CONNECTED_SITE = 60
NM_STATE_CONNECTED_GLOBAL = 70
class NMConnectivityState:
"""Connectvity state
As per https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMConnectivityState
"""
NM_CONNECTIVITY_UNKNOWN = 0
NM_CONNECTIVITY_NONE = 1
NM_CONNECTIVITY_PORTAL = 2
NM_CONNECTIVITY_LIMITED = 3
NM_CONNECTIVITY_FULL = 4
class NMActiveConnectionState:
"""Active connection state
As per https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMActiveConnectionState
"""
NM_ACTIVE_CONNECTION_STATE_UNKNOWN = 0
NM_ACTIVE_CONNECTION_STATE_ACTIVATING = 1
NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2
NM_ACTIVE_CONNECTION_STATE_DEACTIVATING = 3
NM_ACTIVE_CONNECTION_STATE_DEACTIVATED = 4
class InfrastructureMode:
"""Infrastructure mode
As per https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NM80211Mode
"""
NM_802_11_MODE_UNKNOWN = 0
NM_802_11_MODE_ADHOC = 1
NM_802_11_MODE_INFRA = 2
NM_802_11_MODE_AP = 3
NAME_MAP = {
NM_802_11_MODE_UNKNOWN: "unknown",
NM_802_11_MODE_ADHOC: "adhoc",
NM_802_11_MODE_INFRA: "infrastructure",
NM_802_11_MODE_AP: "access-point",
}
class DeviceState:
"""Device states
As per https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMDeviceState
"""
UNKNOWN = 0
UNMANAGED = 10
UNAVAILABLE = 20
DISCONNECTED = 30
PREPARE = 40
CONFIG = 50
NEED_AUTH = 60
IP_CONFIG = 70
IP_CHECK = 80
SECONDARIES = 90
ACTIVATED = 100
DEACTIVATING = 110
FAILED = 120
class NM80211ApSecurityFlags:
"""Security flags
As per https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NM80211ApSecurityFlags
"""
NM_802_11_AP_SEC_NONE = 0x00000000
NM_802_11_AP_SEC_PAIR_WEP40 = 0x00000001
NM_802_11_AP_SEC_PAIR_WEP104 = 0x00000002
NM_802_11_AP_SEC_PAIR_TKIP = 0x00000004
NM_802_11_AP_SEC_PAIR_CCMP = 0x00000008
NM_802_11_AP_SEC_GROUP_WEP40 = 0x00000010
NM_802_11_AP_SEC_GROUP_WEP104 = 0x00000020
NM_802_11_AP_SEC_GROUP_TKIP = 0x00000040
NM_802_11_AP_SEC_GROUP_CCMP = 0x00000080
NM_802_11_AP_SEC_KEY_MGMT_PSK = 0x00000100
NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x00000200
NAME_MAP = {
NM_802_11_AP_SEC_KEY_MGMT_PSK: {"key-mgmt": "wpa-psk", "auth-alg": "open"},
}
class NM80211ApFlags:
"""Device flags
As per https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NM80211ApFlags
"""
NM_802_11_AP_FLAGS_NONE = 0x00000000
NM_802_11_AP_FLAGS_PRIVACY = 0x00000001
def activate_connection(self, conn, dev, ap):
# find a new name
count = 0
active_connections = dbusmock.get_object(MANAGER_OBJ).Get(MANAGER_IFACE, "ActiveConnections")
while True:
path = dbus.ObjectPath("/org/freedesktop/NetworkManager/ActiveConnection/" + str(count))
if path not in active_connections:
break
count += 1
state = dbus.UInt32(NMActiveConnectionState.NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
devices = []
if str(dev) != "/":
devices.append(dev)
return dbus.ObjectPath(AddActiveConnection(self, devices, conn, ap, str(count), state))
def deactivate_connection(self, active_conn_path):
NM = dbusmock.get_object(MANAGER_OBJ)
for dev_path in NM.GetDevices():
RemoveActiveConnection(self, dev_path, active_conn_path)
def add_and_activate_connection(self, conn_conf, dev, ap):
name = ap.rsplit("/", 1)[1]
RemoveWifiConnection(self, dev, "/org/freedesktop/NetworkManager/Settings/" + name)
try:
raw_ssid = "".join([chr(byte) for byte in conn_conf["802-11-wireless"]["ssid"]])
except KeyError:
raw_ssid = dbusmock.get_object(ap).props["org.freedesktop.NetworkManager.AccessPoint"]["Ssid"].decode()
wifi_conn = dbus.ObjectPath(AddWiFiConnection(self, dev, name, raw_ssid, ""))
active_conn = activate_connection(self, wifi_conn, dev, ap)
return (wifi_conn, active_conn)
def get_device_by_ip_iface(_self, iface):
NM = dbusmock.get_object(MANAGER_OBJ)
for dev_path in NM.GetDevices():
dev_obj = dbusmock.get_object(dev_path)
interface = dev_obj.Get(DEVICE_IFACE, "Interface")
if interface == iface:
return dev_path
return None
def set_networking_enabled(self, networking_enabled):
if networking_enabled:
SetGlobalConnectionState(self, NMState.NM_STATE_CONNECTING)
SetGlobalConnectionState(self, NMState.NM_STATE_CONNECTED_LOCAL)
SetGlobalConnectionState(self, NMState.NM_STATE_CONNECTED_GLOBAL)
else:
SetGlobalConnectionState(self, NMState.NM_STATE_DISCONNECTING)
SetGlobalConnectionState(self, NMState.NM_STATE_DISCONNECTED)
SetGlobalConnectionState(self, NMState.NM_STATE_ASLEEP)
SetNetworkingEnabled(self, networking_enabled)
def load(mock, parameters):
manager_props = {
"ActiveConnections": dbus.Array([], signature="o"),
"Devices": dbus.Array([], signature="o"),
"NetworkingEnabled": parameters.get("NetworkingEnabled", True),
"Connectivity": parameters.get("Connectivity", dbus.UInt32(NMConnectivityState.NM_CONNECTIVITY_FULL)),
"State": parameters.get("State", dbus.UInt32(NMState.NM_STATE_CONNECTED_GLOBAL)),
"Startup": False,
"Version": parameters.get("Version", "0.9.6.0"),
"WimaxEnabled": parameters.get("WimaxEnabled", True),
"WimaxHardwareEnabled": parameters.get("WimaxHardwareEnabled", True),
"WirelessEnabled": parameters.get("WirelessEnabled", True),
"WirelessHardwareEnabled": parameters.get("WirelessHardwareEnabled", True),
"WwanEnabled": parameters.get("WwanEnabled", False),
"WwanHardwareEnabled": parameters.get("WwanHardwareEnabled", True),
}
manager_methods = [
("GetDevices", "", "ao", 'ret = [k for k in objects.keys() if "/Devices" in k]'),
("GetPermissions", "", "a{ss}", "ret = {}"),
("state", "", "u", f"ret = self.Get('{MANAGER_IFACE}', 'State')"),
("CheckConnectivity", "", "u", f"ret = self.Get('{MANAGER_IFACE}', 'Connectivity')"),
("ActivateConnection", "ooo", "o", "ret = self.activate_connection(self, args[0], args[1], args[2])"),
("DeactivateConnection", "o", "", "self.deactivate_connection(self, args[0])"),
(
"AddAndActivateConnection",
"a{sa{sv}}oo",
"oo",
"ret = self.add_and_activate_connection(self, args[0], args[1], args[2])",
),
("GetDeviceByIpIface", "s", "o", "ret = self.get_device_by_ip_iface(self, args[0])"),
("Reload", "u", "", ""),
("Enable", "b", "", "self.set_networking_enabled(self, args[0])"),
]
mock.AddObject(MANAGER_OBJ, MANAGER_IFACE, manager_props, manager_methods)
mock.object_manager_emit_added(MANAGER_OBJ)
obj = dbusmock.get_object(MANAGER_OBJ)
obj.activate_connection = activate_connection
obj.deactivate_connection = deactivate_connection
obj.add_and_activate_connection = add_and_activate_connection
obj.get_device_by_ip_iface = get_device_by_ip_iface
obj.set_networking_enabled = set_networking_enabled
settings_props = {"Hostname": "hostname", "CanModify": True, "Connections": dbus.Array([], signature="o")}
settings_methods = [
("ListConnections", "", "ao", f"ret = self.Get('{SETTINGS_IFACE}', 'Connections')"),
("GetConnectionByUuid", "s", "o", "ret = self.SettingsGetConnectionByUuid(args[0])"),
("AddConnection", "a{sa{sv}}", "o", "ret = self.SettingsAddConnection(args[0])"),
("AddConnectionUnsaved", "a{sa{sv}}", "o", "ret = self.SettingsAddConnection(args[0])"),
("SaveHostname", "s", "", ""),
]
mock.AddObject(SETTINGS_OBJ, SETTINGS_IFACE, settings_props, settings_methods)
mock.object_manager_emit_added(SETTINGS_OBJ)
@dbus.service.method(MOCK_IFACE, in_signature="sssv", out_signature="")
def SetProperty(_self, path, iface, name, value):
obj = dbusmock.get_object(path)
obj.Set(iface, name, value)
obj.EmitSignal(iface, "PropertiesChanged", "a{sv}", [{name: value}])
@dbus.service.method(MOCK_IFACE, in_signature="u", out_signature="")
def SetGlobalConnectionState(self, state):
self.SetProperty(MANAGER_OBJ, MANAGER_IFACE, "State", dbus.UInt32(state))
self.EmitSignal(MANAGER_IFACE, "StateChanged", "u", [state])
@dbus.service.method(MOCK_IFACE, in_signature="u", out_signature="")
def SetConnectivity(self, connectivity):
self.SetProperty(MANAGER_OBJ, MANAGER_IFACE, "Connectivity", dbus.UInt32(connectivity))
@dbus.service.method(MOCK_IFACE, in_signature="b", out_signature="")
def SetNetworkingEnabled(self, networking_enabled):
self.SetProperty(MANAGER_OBJ, MANAGER_IFACE, "NetworkingEnabled", dbus.Boolean(networking_enabled))
@dbus.service.method(MOCK_IFACE, in_signature="ss", out_signature="")
def SetDeviceActive(_self, device_path, active_connection_path):
dev_obj = dbusmock.get_object(device_path)
dev_obj.Set(DEVICE_IFACE, "ActiveConnection", dbus.ObjectPath(active_connection_path))
old_state = dev_obj.Get(DEVICE_IFACE, "State")
dev_obj.Set(DEVICE_IFACE, "State", dbus.UInt32(DeviceState.ACTIVATED))
dev_obj.Set(DEVICE_IFACE, "StateReason", (dbus.UInt32(DeviceState.ACTIVATED), dbus.UInt32(0)))
dev_obj.EmitSignal(
DEVICE_IFACE, "StateChanged", "uuu", [dbus.UInt32(DeviceState.ACTIVATED), old_state, dbus.UInt32(1)]
)
@dbus.service.method(MOCK_IFACE, in_signature="s", out_signature="")
def SetDeviceDisconnected(_self, device_path):
dev_obj = dbusmock.get_object(device_path)
dev_obj.Set(DEVICE_IFACE, "ActiveConnection", dbus.ObjectPath("/"))
old_state = dev_obj.Get(DEVICE_IFACE, "State")
dev_obj.Set(DEVICE_IFACE, "State", dbus.UInt32(DeviceState.DISCONNECTED))
dev_obj.Set(DEVICE_IFACE, "StateReason", (dbus.UInt32(DeviceState.DISCONNECTED), dbus.UInt32(0)))
dev_obj.EmitSignal(
DEVICE_IFACE, "StateChanged", "uuu", [dbus.UInt32(DeviceState.DISCONNECTED), old_state, dbus.UInt32(1)]
)
@dbus.service.method(MOCK_IFACE, in_signature="ssi", out_signature="s")
def AddEthernetDevice(self, device_name, iface_name, state):
"""Add an ethernet device.
You have to specify device_name, device interface name (e. g. eth0), and
state. You can use the predefined DeviceState values (e. g.
DeviceState.ACTIVATED) or supply a numeric value. For valid state values
please visit
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMDeviceState
Please note that this does not set any global properties.
Returns the new object path.
"""
path = "/org/freedesktop/NetworkManager/Devices/" + device_name
wired_props = {
"Carrier": False,
"HwAddress": dbus.String("78:DD:08:D2:3D:43"),
"PermHwAddress": dbus.String("78:DD:08:D2:3D:43"),
"Speed": dbus.UInt32(0),
}
self.AddObject(path, "org.freedesktop.NetworkManager.Device.Wired", wired_props, [])
props = {
"DeviceType": dbus.UInt32(1),
"State": dbus.UInt32(state),
"StateReason": (dbus.UInt32(state), dbus.UInt32(0)),
"Interface": iface_name,
"ActiveConnection": dbus.ObjectPath("/"),
"AvailableConnections": dbus.Array([], signature="o"),
"AutoConnect": False,
"Managed": True,
"Driver": "dbusmock",
"IpInterface": "",
}
obj = dbusmock.get_object(path)
obj.AddProperties(DEVICE_IFACE, props)
self.object_manager_emit_added(path)
NM = dbusmock.get_object(MANAGER_OBJ)
devices = NM.Get(MANAGER_IFACE, "Devices")
devices.append(path)
NM.Set(MANAGER_IFACE, "Devices", devices)
NM.EmitSignal("org.freedesktop.NetworkManager", "DeviceAdded", "o", [path])
return path
@dbus.service.method(MOCK_IFACE, in_signature="ssi", out_signature="s")
def AddWiFiDevice(self, device_name, iface_name, state):
"""Add a WiFi Device.
You have to specify device_name, device interface name (e. g. wlan0) and
state. You can use the predefined DeviceState values (e. g.
DeviceState.ACTIVATED) or supply a numeric value. For valid state values,
please visit
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMDeviceState
Please note that this does not set any global properties.
Returns the new object path.
"""
path = "/org/freedesktop/NetworkManager/Devices/" + device_name
self.AddObject(
path,
WIRELESS_DEVICE_IFACE,
{
"HwAddress": dbus.String("11:22:33:44:55:66"),
"PermHwAddress": dbus.String("11:22:33:44:55:66"),
"Bitrate": dbus.UInt32(5400),
"Mode": dbus.UInt32(2),
"WirelessCapabilities": dbus.UInt32(255),
"AccessPoints": dbus.Array([], signature="o"),
},
[
("GetAccessPoints", "", "ao", "ret = self.access_points"),
("GetAllAccessPoints", "", "ao", "ret = self.access_points"),
("RequestScan", "a{sv}", "", ""),
],
)
dev_obj = dbusmock.get_object(path)
dev_obj.access_points = []
dev_obj.AddProperties(
DEVICE_IFACE,
{
"ActiveConnection": dbus.ObjectPath("/"),
"AvailableConnections": dbus.Array([], signature="o"),
"AutoConnect": False,
"Managed": True,
"Driver": "dbusmock",
"DeviceType": dbus.UInt32(2),
"State": dbus.UInt32(state),
"StateReason": (dbus.UInt32(state), dbus.UInt32(0)),
"Interface": iface_name,
"IpInterface": iface_name,
},
)
self.object_manager_emit_added(path)
NM = dbusmock.get_object(MANAGER_OBJ)
devices = NM.Get(MANAGER_IFACE, "Devices")
devices.append(path)
NM.Set(MANAGER_IFACE, "Devices", devices)
NM.EmitSignal("org.freedesktop.NetworkManager", "DeviceAdded", "o", [path])
return path
@dbus.service.method(MOCK_IFACE, in_signature="ssssuuuyu", out_signature="s")
def AddAccessPoint(self, dev_path, ap_name, ssid, hw_address, mode, frequency, rate, strength, security):
"""Add an access point to an existing WiFi device.
You have to specify WiFi Device path, Access Point object name,
ssid, hw_address, mode, frequency, rate, strength and security.
For valid access point property values, please visit
https://developer.gnome.org/NetworkManager/unstable/gdbus-org.freedesktop.NetworkManager.AccessPoint.html
Please note that this does not set any global properties.
Returns the new object path.
"""
dev_obj = dbusmock.get_object(dev_path)
ap_path = "/org/freedesktop/NetworkManager/AccessPoint/" + ap_name
if ap_path in dev_obj.access_points:
raise dbus.exceptions.DBusException(
f"Access point {ap_name} on device {dev_path} already exists", name=MANAGER_IFACE + ".AlreadyExists"
)
flags = NM80211ApFlags.NM_802_11_AP_FLAGS_PRIVACY
if security == NM80211ApSecurityFlags.NM_802_11_AP_SEC_NONE:
flags = NM80211ApFlags.NM_802_11_AP_FLAGS_NONE
self.AddObject(
ap_path,
ACCESS_POINT_IFACE,
{
"Ssid": dbus.ByteArray(ssid.encode("UTF-8")),
"HwAddress": dbus.String(hw_address),
"Flags": dbus.UInt32(flags),
"LastSeen": dbus.Int32(1),
"Frequency": dbus.UInt32(frequency),
"MaxBitrate": dbus.UInt32(rate),
"Mode": dbus.UInt32(mode),
"RsnFlags": dbus.UInt32(security),
"WpaFlags": dbus.UInt32(security),
"Strength": dbus.Byte(strength),
},
[],
)
self.object_manager_emit_added(ap_path)
dev_obj.access_points.append(ap_path)
aps = dev_obj.Get(WIRELESS_DEVICE_IFACE, "AccessPoints")
aps.append(ap_path)
dev_obj.Set(WIRELESS_DEVICE_IFACE, "AccessPoints", aps)
dev_obj.EmitSignal(WIRELESS_DEVICE_IFACE, "AccessPointAdded", "o", [ap_path])
return ap_path
@dbus.service.method(MOCK_IFACE, in_signature="ssss", out_signature="s")
def AddWiFiConnection(self, dev_path, connection_name, ssid_name, _key_mgmt):
"""Add an available connection to an existing WiFi device and access point.
You have to specify WiFi Device path, Connection object name,
SSID and key management.
The SSID must match one of the previously created access points.
Please note that this only sets minimal global properties in ipv{4,6}, no others.
Returns the new object path.
"""
dev_obj = dbusmock.get_object(dev_path)
connection_path = "/org/freedesktop/NetworkManager/Settings/" + connection_name
connections = dev_obj.Get(DEVICE_IFACE, "AvailableConnections")
settings_obj = dbusmock.get_object(SETTINGS_OBJ)
main_connections = settings_obj.ListConnections()
ssid = ssid_name.encode("UTF-8")
# Find the access point by ssid
access_point = None
access_points = dev_obj.access_points
access_point_path = None
for ap_path in access_points:
ap = dbusmock.get_object(ap_path)
if ap.Get(ACCESS_POINT_IFACE, "Ssid") == ssid:
access_point = ap
access_point_path = ap_path
break
if not access_point:
raise dbus.exceptions.DBusException(
f"Access point with SSID [{ssid_name}] could not be found", name=MANAGER_IFACE + ".DoesNotExist"
)
hw_address = access_point.Get(ACCESS_POINT_IFACE, "HwAddress")
mode = access_point.Get(ACCESS_POINT_IFACE, "Mode")
security = access_point.Get(ACCESS_POINT_IFACE, "WpaFlags")
if connection_path in connections or connection_path in main_connections:
raise dbus.exceptions.DBusException(
f"Connection {connection_name} on device {dev_path} already exists", name=MANAGER_IFACE + ".AlreadyExists"
)
# Parse mac address string into byte array
mac_bytes = binascii.unhexlify(hw_address.replace(":", ""))
settings = {
"802-11-wireless": {
"seen-bssids": [hw_address],
"ssid": dbus.ByteArray(ssid),
"mac-address": dbus.ByteArray(mac_bytes),
"mode": InfrastructureMode.NAME_MAP[mode],
},
"connection": {
"timestamp": dbus.UInt64(1374828522),
"type": "802-11-wireless",
"id": ssid_name,
"uuid": str(uuid.uuid4()),
},
"ipv4": {
"method": "auto",
},
"ipv6": {
"method": "auto",
},
}
if security != NM80211ApSecurityFlags.NM_802_11_AP_SEC_NONE:
settings["802-11-wireless"]["security"] = "802-11-wireless-security"
settings["802-11-wireless-security"] = NM80211ApSecurityFlags.NAME_MAP[security]
self.AddObject(
connection_path,
CSETTINGS_IFACE,
{"Unsaved": False},
[
("Delete", "", "", "self.ConnectionDelete(self)"),
("GetSettings", "", "a{sa{sv}}", "ret = self.ConnectionGetSettings(self)"),
("GetSecrets", "s", "a{sa{sv}}", "ret = self.ConnectionGetSecrets(self, args[0])"),
("Update", "a{sa{sv}}", "", "self.ConnectionUpdate(self, args[0])"),
],
)
self.object_manager_emit_added(connection_path)
connection_obj = dbusmock.get_object(connection_path)
connection_obj.settings = settings
connection_obj.connection_path = connection_path
connection_obj.ConnectionDelete = ConnectionDelete
connection_obj.ConnectionGetSettings = ConnectionGetSettings
connection_obj.ConnectionGetSecrets = ConnectionGetSecrets
connection_obj.ConnectionUpdate = ConnectionUpdate
connections.append(dbus.ObjectPath(connection_path))
dev_obj.Set(DEVICE_IFACE, "AvailableConnections", connections)
main_connections.append(connection_path)
settings_obj.Set(SETTINGS_IFACE, "Connections", main_connections)
settings_obj.EmitSignal(SETTINGS_IFACE, "NewConnection", "o", [access_point_path])
return connection_path
@dbus.service.method(MOCK_IFACE, in_signature="assssu", out_signature="s")
def AddActiveConnection(self, devices, connection_device, specific_object, name, state):
"""Add an active connection to an existing WiFi device.
You have to a list of the involved WiFi devices, the connection path,
the access point path, ActiveConnection object name and connection
state.
Please note that this does not set any global properties.
Returns the new object path.
"""
conn_obj = dbusmock.get_object(connection_device)
settings = conn_obj.settings
conn_uuid = settings["connection"]["uuid"]
conn_type = settings["connection"]["type"]
conn_id = settings["connection"]["id"]
device_objects = [dbus.ObjectPath(dev) for dev in devices]
active_connection_path = "/org/freedesktop/NetworkManager/ActiveConnection/" + name
self.AddObject(
active_connection_path,
ACTIVE_CONNECTION_IFACE,
{
"Devices": dbus.Array(device_objects, signature="o"),
"Default6": False,
"Default": True,
"Type": conn_type,
"Vpn": (conn_type == "vpn"),
"Connection": dbus.ObjectPath(connection_device),
"Master": dbus.ObjectPath("/"),
"SpecificObject": dbus.ObjectPath(specific_object),
"Uuid": conn_uuid,
"State": dbus.UInt32(state),
"Id": conn_id,
},
[],
)
for dev_path in devices:
self.SetDeviceActive(dev_path, active_connection_path)
self.object_manager_emit_added(active_connection_path)
NM = dbusmock.get_object(MANAGER_OBJ)
active_connections = NM.Get(MANAGER_IFACE, "ActiveConnections")
active_connections.append(dbus.ObjectPath(active_connection_path))
NM.SetProperty(MANAGER_OBJ, MANAGER_IFACE, "ActiveConnections", active_connections)
return active_connection_path
@dbus.service.method(MOCK_IFACE, in_signature="ss", out_signature="")
def RemoveAccessPoint(self, dev_path, ap_path):
"""Remove the specified access point.
You have to specify the device to remove the access point from, and the
path of the access point.
Please note that this does not set any global properties.
"""
dev_obj = dbusmock.get_object(dev_path)
aps = dev_obj.Get(WIRELESS_DEVICE_IFACE, "AccessPoints")
aps.remove(ap_path)
dev_obj.Set(WIRELESS_DEVICE_IFACE, "AccessPoints", aps)
dev_obj.access_points.remove(ap_path)
dev_obj.EmitSignal(WIRELESS_DEVICE_IFACE, "AccessPointRemoved", "o", [ap_path])
self.object_manager_emit_removed(ap_path)
self.RemoveObject(ap_path)
@dbus.service.method(MOCK_IFACE, in_signature="ss", out_signature="")
def RemoveWifiConnection(self, dev_path, connection_path):
"""Remove the specified WiFi connection.
You have to specify the device to remove the connection from, and the
path of the Connection.
Please note that this does not set any global properties.
"""
dev_obj = dbusmock.get_object(dev_path)
settings_obj = dbusmock.get_object(SETTINGS_OBJ)
connections = dev_obj.Get(DEVICE_IFACE, "AvailableConnections")
main_connections = settings_obj.ListConnections()
if connection_path not in connections and connection_path not in main_connections:
return
connections.remove(dbus.ObjectPath(connection_path))
dev_obj.Set(DEVICE_IFACE, "AvailableConnections", connections)
main_connections.remove(connection_path)
settings_obj.Set(SETTINGS_IFACE, "Connections", main_connections)
settings_obj.EmitSignal(SETTINGS_IFACE, "ConnectionRemoved", "o", [connection_path])
connection_obj = dbusmock.get_object(connection_path)
connection_obj.EmitSignal(CSETTINGS_IFACE, "Removed", "", [])
self.object_manager_emit_removed(connection_path)
self.RemoveObject(connection_path)
@dbus.service.method(MOCK_IFACE, in_signature="ss", out_signature="")
def RemoveActiveConnection(self, dev_path, active_connection_path):
"""Remove the specified ActiveConnection.
You have to specify the device to remove the connection from, and the
path of the ActiveConnection.
Please note that this does not set any global properties.
"""
self.SetDeviceDisconnected(dev_path)
NM = dbusmock.get_object(MANAGER_OBJ)
active_connections = NM.Get(MANAGER_IFACE, "ActiveConnections")
if active_connection_path not in active_connections:
return
active_connections.remove(dbus.ObjectPath(active_connection_path))
NM.SetProperty(MANAGER_OBJ, MANAGER_IFACE, "ActiveConnections", active_connections)
self.object_manager_emit_removed(active_connection_path)
self.RemoveObject(active_connection_path)
@dbus.service.method(SETTINGS_IFACE, in_signature="a{sa{sv}}", out_signature="o")
def SettingsAddConnection(self, connection_settings):
"""Add a connection.
connection_settings is a String String Variant Map Map. See
https://developer.gnome.org/NetworkManager/0.9/spec.html #type-String_String_Variant_Map_Map
If you omit uuid, this method adds one for you.
"""
if "uuid" not in connection_settings["connection"]:
connection_settings["connection"]["uuid"] = str(uuid.uuid4())
NM = dbusmock.get_object(MANAGER_OBJ)
settings_obj = dbusmock.get_object(SETTINGS_OBJ)
main_connections = settings_obj.ListConnections()
# Mimic how NM names connections
count = 0
while True:
connection_obj_path = dbus.ObjectPath(SETTINGS_OBJ + "/" + str(count))
if connection_obj_path not in main_connections:
break
count += 1
connection_path = str(connection_obj_path)
self.AddObject(
connection_path,
CSETTINGS_IFACE,
{"Unsaved": False},
[
("Delete", "", "", "self.ConnectionDelete(self)"),
("GetSettings", "", "a{sa{sv}}", "ret = self.ConnectionGetSettings(self)"),
("GetSecrets", "s", "a{sa{sv}}", "ret = self.ConnectionGetSecrets(self, args[0])"),
("Update", "a{sa{sv}}", "", "self.ConnectionUpdate(self, args[0])"),
],
)
self.object_manager_emit_added(connection_path)
connection_obj = dbusmock.get_object(connection_path)
connection_obj.settings = connection_settings
connection_obj.connection_path = connection_path
connection_obj.ConnectionDelete = ConnectionDelete
connection_obj.ConnectionGetSettings = ConnectionGetSettings
connection_obj.ConnectionGetSecrets = ConnectionGetSecrets
connection_obj.ConnectionUpdate = ConnectionUpdate
main_connections.append(connection_path)
settings_obj.Set(SETTINGS_IFACE, "Connections", main_connections)
settings_obj.EmitSignal(SETTINGS_IFACE, "NewConnection", "o", [connection_path])
auto_connect = False
if "autoconnect" in connection_settings["connection"]:
auto_connect = connection_settings["connection"]["autoconnect"]
if auto_connect:
dev = None
devices = NM.GetDevices()
# Grab the first device.
if len(devices) > 0:
dev = devices[0]
if dev:
activate_connection(NM, connection_path, dev, connection_path)
return connection_path
@dbus.service.method(SETTINGS_IFACE, in_signature="s", out_signature="o")
def SettingsGetConnectionByUuid(self, conn_uuid):
conns = self.ListConnections()
for o in conns:
self.conn = dbusmock.get_object(o)
settings = self.conn.GetSettings()
if settings["connection"]["uuid"] == conn_uuid:
return o
raise dbus.exceptions.DBusException("No connection with the UUID was found.")
def ConnectionUpdate(self, settings):
"""Update settings on a connection.
settings is a String String Variant Map Map. See
https://developer.gnome.org/NetworkManager/0.9/spec.html#type-String_String_Variant_Map_Map
"""
connection_path = self.connection_path
NM = dbusmock.get_object(MANAGER_OBJ)
settings_obj = dbusmock.get_object(SETTINGS_OBJ)
main_connections = settings_obj.ListConnections()
if connection_path not in main_connections:
raise dbus.exceptions.DBusException(
f"Connection {connection_path} does not exist",
name=MANAGER_IFACE + ".DoesNotExist",
)
# Take care not to overwrite the secrets
for setting_name in settings:
setting = settings[setting_name]
for k in setting:
if setting_name not in self.settings:
self.settings[setting_name] = {}
self.settings[setting_name][k] = setting[k]
self.EmitSignal(CSETTINGS_IFACE, "Updated", "", [])
auto_connect = False
if "autoconnect" in settings["connection"]:
auto_connect = settings["connection"]["autoconnect"]
if auto_connect:
dev = None
devices = NM.GetDevices()
# Grab the first device.
if len(devices) > 0:
dev = devices[0]
if dev:
activate_connection(NM, connection_path, dev, connection_path)
return connection_path
def ConnectionGetSettings(self):
# Deep copy the settings with the secrets stripped
# out. (NOTE: copy.deepcopy doesn't work with dbus
# types).
settings = {}
for setting_name in self.settings:
setting = self.settings[setting_name]
for k in setting:
if k != "secrets":
if setting_name not in settings:
settings[setting_name] = {}
settings[setting_name][k] = setting[k]
return settings
def ConnectionGetSecrets(self, setting):
settings = self.settings[setting]
if "secrets" in settings:
secrets = {setting: {"secrets": settings["secrets"]}}
else:
secrets = {setting: {"secrets": {"no-secrets": True}}}
return secrets
def ConnectionDelete(self):
"""Deletes a connection.
This also
* removes the deleted connection from any device,
* removes any active connection(s) it might be associated with,
* removes it from the Settings interface,
* as well as deletes the object from the mock.
Note: If this was the only active connection, we change the global
connection state.
"""
connection_path = self.connection_path
NM = dbusmock.get_object(MANAGER_OBJ)
settings_obj = dbusmock.get_object(SETTINGS_OBJ)
# Find the associated active connection(s).
active_connections = NM.Get(MANAGER_IFACE, "ActiveConnections")
associated_active_connections = []
for ac in active_connections:
ac_obj = dbusmock.get_object(ac)
ac_con = ac_obj.Get(ACTIVE_CONNECTION_IFACE, "Connection")
if ac_con == connection_path:
associated_active_connections.append(ac)
# We found that the connection we are deleting are associated to all
# active connections and subsequently set the global state to
# disconnected.
if len(active_connections) == len(associated_active_connections):
self.SetGlobalConnectionState(NMState.NM_STATE_DISCONNECTED)
# Remove the connection from all associated devices.
# We also remove all associated active connections.
for dev_path in NM.GetDevices():
dev_obj = dbusmock.get_object(dev_path)
connections = dev_obj.Get(DEVICE_IFACE, "AvailableConnections")
for ac in associated_active_connections:
NM.RemoveActiveConnection(dev_path, ac)
if connection_path not in connections:
continue
connections.remove(dbus.ObjectPath(connection_path))
dev_obj.Set(DEVICE_IFACE, "AvailableConnections", connections)
# Remove the connection from the settings interface
main_connections = settings_obj.ListConnections()
if connection_path not in main_connections:
return
main_connections.remove(connection_path)
settings_obj.Set(SETTINGS_IFACE, "Connections", main_connections)
settings_obj.EmitSignal(SETTINGS_IFACE, "ConnectionRemoved", "o", [connection_path])
# Remove the connection from the mock
connection_obj = dbusmock.get_object(connection_path)
connection_obj.EmitSignal(CSETTINGS_IFACE, "Removed", "", [])
self.object_manager_emit_removed(connection_path)
self.RemoveObject(connection_path)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/notification_daemon.py 0000644 0001751 0001751 00000003550 15071377476 024674 0 ustar 00runner runner """notification-daemon mock template
This creates the expected methods and properties of the notification-daemon
services, but no devices. You can specify non-default capabilities in
"parameters".
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
BUS_NAME = "org.freedesktop.Notifications"
MAIN_OBJ = "/org/freedesktop/Notifications"
MAIN_IFACE = "org.freedesktop.Notifications"
SYSTEM_BUS = False
# default capabilities, can be modified with "capabilities" parameter
default_caps = [
"body",
"body-markup",
"icon-static",
"image/svg+xml",
"private-synchronous",
"append",
"private-icon-only",
"truncation",
]
def load(mock, parameters):
caps = parameters["capabilities"].split() if "capabilities" in parameters else default_caps
# next notification ID
mock.next_id = 1
mock.AddMethods(
MAIN_IFACE,
[
("GetCapabilities", "", "as", f"ret = {caps!r}"),
(
"CloseNotification",
"i",
"",
"if args[0] < self.next_id: self.EmitSignal('', 'NotificationClosed', 'uu', [args[0], 1])",
),
("GetServerInformation", "", "ssss", 'ret = ("mock-notify", "test vendor", "1.0", "1.1")'),
(
"Notify",
"susssasa{sv}i",
"u",
"""if args[1]:
ret = args[1]
else:
ret = self.next_id
self.next_id += 1
""",
),
],
)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/ofono.py 0000644 0001751 0001751 00000043431 15071377476 022005 0 ustar 00runner runner """ofonod D-Bus mock template"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import dbus
import dbusmock
BUS_NAME = "org.ofono"
MAIN_OBJ = "/"
MAIN_IFACE = "org.ofono.Manager"
SYSTEM_BUS = True
NOT_IMPLEMENTED = 'raise dbus.exceptions.DBusException("not implemented", name="org.ofono.Error.NotImplemented")'
# interface org.ofono.Manager {
# methods:
# GetModems(out a(oa{sv}) modems);
# signals:
# ModemAdded(o path,
# a{sv} properties);
# ModemRemoved(o path);
# };
_parameters = {}
def load(mock, parameters):
global _parameters # pylint: disable=global-statement
mock.modems = [] # object paths
mock.modem_serial_counter = 0
mock.imsi_counter = 0
mock.iccid_counter = 0
_parameters = parameters
mock.AddMethod(
MAIN_IFACE,
"GetModems",
"",
"a(oa{sv})",
'ret = [(m, objects[m].GetAll("org.ofono.Modem")) for m in self.modems]',
)
if not parameters.get("no_modem", False):
mock.AddModem(parameters.get("ModemName", "ril_0"), {})
# interface org.ofono.Modem {
# methods:
# GetProperties(out a{sv} properties);
# SetProperty(in s property,
# in v value);
# signals:
# PropertyChanged(s name,
# v value);
# };
@dbus.service.method(dbusmock.MOCK_IFACE, in_signature="sa{sv}", out_signature="s")
def AddModem(self, name, _properties):
"""Convenience method to add a modem
You have to specify a device name which must be a valid part of an object
path, e. g. "mock_ac". For future extensions you can specify a "properties"
array, but no extra properties are supported for now.
Returns the new object path.
"""
path = "/" + name
self.AddObject(
path,
"org.ofono.Modem",
{
"Online": dbus.Boolean(True),
"Powered": dbus.Boolean(True),
"Lockdown": dbus.Boolean(False),
"Emergency": dbus.Boolean(False),
"Manufacturer": dbus.String("Fakesys"),
"Model": dbus.String("Mock Modem"),
"Revision": dbus.String("0815.42"),
"Serial": dbus.String(new_modem_serial(self)),
"Type": dbus.String("hardware"),
"Interfaces": [
"org.ofono.CallVolume",
"org.ofono.VoiceCallManager",
"org.ofono.NetworkRegistration",
"org.ofono.SimManager",
# 'org.ofono.MessageManager',
"org.ofono.ConnectionManager",
# 'org.ofono.NetworkTime'
],
# 'Features': ['sms', 'net', 'gprs', 'sim']
"Features": ["gprs", "net"],
},
[
("GetProperties", "", "a{sv}", 'ret = self.GetAll("org.ofono.Modem")'),
(
"SetProperty",
"sv",
"",
'self.Set("org.ofono.Modem", args[0], args[1]); '
'self.EmitSignal("org.ofono.Modem", "PropertyChanged",'
' "sv", [args[0], args[1]])',
),
],
)
obj = dbusmock.mockobject.objects[path]
obj.name = name
add_voice_call_api(obj)
add_netreg_api(obj)
add_simmanager_api(self, obj)
add_connectionmanager_api(obj)
self.modems.append(path)
props = obj.GetAll("org.ofono.Modem", dbus_interface=dbus.PROPERTIES_IFACE)
self.EmitSignal(MAIN_IFACE, "ModemAdded", "oa{sv}", [path, props])
return path
# Generate a new modem serial number so each modem we add gets a unique one.
# Use a counter so that the result is predictable for tests.
def new_modem_serial(mock):
serial = f"12345678-1234-1234-1234-{mock.modem_serial_counter:012d}"
mock.modem_serial_counter += 1
return serial
# Generate a new unique IMSI (start with USA/AT&T 310/150 to match the MCC/MNC SIM properties)
# Use a counter so that the result is predictable for tests.
def new_imsi(mock):
imsi = f"310150{mock.imsi_counter:09d}"
mock.imsi_counter += 1
return imsi
# Generate a new unique ICCID
# Use a counter so that the result is predictable for tests.
def new_iccid(mock):
iccid = f"893581234{mock.iccid_counter:012d}"
mock.iccid_counter += 1
return iccid
# interface org.ofono.VoiceCallManager {
# methods:
# GetProperties(out a{sv} properties);
# Dial(in s number,
# in s hide_callerid,
# out o path);
# Transfer();
# SwapCalls();
# ReleaseAndAnswer();
# ReleaseAndSwap();
# HoldAndAnswer();
# HangupAll();
# PrivateChat(in o call,
# out ao calls);
# CreateMultiparty(out o calls);
# HangupMultiparty();
# SendTones(in s SendTones);
# GetCalls(out a(oa{sv}) calls_with_properties);
# signals:
# Forwarded(s type);
# BarringActive(s type);
# PropertyChanged(s name,
# v value);
# CallAdded(o path,
# a{sv} properties);
# CallRemoved(o path);
# };
def add_voice_call_api(mock):
"""Add org.ofono.VoiceCallManager API to a mock"""
# also add an emergency number which is not a real one, in case one runs a
# test case against a production ofono :-)
mock.AddProperty("org.ofono.VoiceCallManager", "EmergencyNumbers", ["911", "13373"])
mock.calls = [] # object paths
mock.AddMethods(
"org.ofono.VoiceCallManager",
[
("GetProperties", "", "a{sv}", 'ret = self.GetAll("org.ofono.VoiceCallManager")'),
("Transfer", "", "", ""),
("SwapCalls", "", "", ""),
("ReleaseAndAnswer", "", "", ""),
("ReleaseAndSwap", "", "", ""),
("HoldAndAnswer", "", "", ""),
("SendTones", "s", "", ""),
("PrivateChat", "o", "ao", NOT_IMPLEMENTED),
("CreateMultiparty", "", "o", NOT_IMPLEMENTED),
("HangupMultiparty", "", "", NOT_IMPLEMENTED),
(
"GetCalls",
"",
"a(oa{sv})",
'ret = [(c, objects[c].GetAll("org.ofono.VoiceCall")) for c in self.calls]',
),
],
)
@dbus.service.method("org.ofono.VoiceCallManager", in_signature="ss", out_signature="s")
def Dial(self, number, _hide_callerid):
# pylint: disable=protected-access # _object_path
path = f"{self._object_path}/voicecall{(len(self.calls) + 1):02}"
self.AddObject(
path,
"org.ofono.VoiceCall",
{
"State": dbus.String("dialing"),
"LineIdentification": dbus.String(number),
"Name": dbus.String(""),
"Multiparty": dbus.Boolean(False),
"RemoteHeld": dbus.Boolean(False),
"RemoteMultiparty": dbus.Boolean(False),
"Emergency": dbus.Boolean(False),
},
[
("GetProperties", "", "a{sv}", 'ret = self.GetAll("org.ofono.VoiceCall")'),
("Deflect", "s", "", NOT_IMPLEMENTED),
(
"Hangup",
"",
"",
"self.parent.calls.remove(self._object_path);"
"self.parent.RemoveObject(self._object_path);"
'self.EmitSignal("org.ofono.VoiceCallManager", "CallRemoved", "o", [self._object_path])',
),
("Answer", "", "", NOT_IMPLEMENTED),
],
)
obj = dbusmock.mockobject.objects[path]
obj.parent = self
self.calls.append(path)
self.EmitSignal("org.ofono.VoiceCallManager", "CallAdded", "oa{sv}", [path, obj.GetProperties()])
return path
@dbus.service.method("org.ofono.VoiceCallManager", in_signature="", out_signature="")
def HangupAll(self):
print("XXX HangupAll", self.calls)
for c in list(self.calls): # needs a copy
dbusmock.mockobject.objects[c].Hangup()
assert self.calls == []
# interface org.ofono.NetworkRegistration {
# methods:
# GetProperties(out a{sv} properties);
# SetProperty(in s property,
# in v value);
# Register();
# GetOperators(out a(oa{sv}) operators_with_properties);
# Scan(out a(oa{sv}) operators_with_properties);
# signals:
# PropertyChanged(s name,
# v value);
# };
#
# for //operator/:
# interface org.ofono.NetworkOperator {
# methods:
# GetProperties(out a{sv} properties);
# Register();
# signals:
# PropertyChanged(s name,
# v value);
# properties:
# };
def get_all_operators(mock):
return f'ret = [(m, objects[m].GetAll("org.ofono.NetworkOperator")) for m in objects if "{mock.name}/operator/" in m]'
def add_netreg_api(mock):
"""Add org.ofono.NetworkRegistration API to a mock"""
# also add an emergency number which is not a real one, in case one runs a
# test case against a production ofono :-)
mock.AddProperties(
"org.ofono.NetworkRegistration",
{
"Mode": "auto",
"Status": "registered",
"LocationAreaCode": _parameters.get("LocationAreaCode", 987),
"CellId": _parameters.get("CellId", 10203),
"MobileCountryCode": _parameters.get("MobileCountryCode", "777"),
"MobileNetworkCode": _parameters.get("MobileNetworkCode", "11"),
"Technology": _parameters.get("Technology", "gsm"),
"Name": _parameters.get("Name", "fake.tel"),
"Strength": _parameters.get("Strength", dbus.Byte(80)),
"BaseStation": _parameters.get("BaseStation", ""),
},
)
mock.AddObject(
f"/{mock.name}/operator/op1",
"org.ofono.NetworkOperator",
{
"Name": _parameters.get("Name", "fake.tel"),
"Status": "current",
"MobileCountryCode": _parameters.get("MobileCountryCode", "777"),
"MobileNetworkCode": _parameters.get("MobileNetworkCode", "11"),
"Technologies": [_parameters.get("Technology", "gsm")],
},
[
("GetProperties", "", "a{sv}", 'ret = self.GetAll("org.ofono.NetworkOperator")'),
("Register", "", "", ""),
],
)
mock.AddMethods(
"org.ofono.NetworkRegistration",
[
("GetProperties", "", "a{sv}", 'ret = self.GetAll("org.ofono.NetworkRegistration")'),
(
"SetProperty",
"sv",
"",
'self.Set("org.ofono.NetworkRegistration", args[0], args[1]); '
'self.EmitSignal("org.ofono.NetworkRegistration", "PropertyChanged", "sv", [args[0], args[1]])',
),
("Register", "", "", ""),
("GetOperators", "", "a(oa{sv})", get_all_operators(mock)),
("Scan", "", "a(oa{sv})", get_all_operators(mock)),
],
)
# interface org.ofono.SimManager {
# methods:
# GetProperties(out a{sv} properties);
# SetProperty(in s property,
# in v value);
# ChangePin(in s type,
# in s oldpin,
# in s newpin);
# EnterPin(in s type,
# in s pin);
# ResetPin(in s type,
# in s puk,
# in s newpin);
# LockPin(in s type,
# in s pin);
# UnlockPin(in s type,
# in s pin);
# GetIcon(in y id,
# out ay icon);
# signals:
# PropertyChanged(s name,
# v value);
# };
def add_simmanager_api(self, mock):
"""Add org.ofono.SimManager API to a mock"""
iface = "org.ofono.SimManager"
mock.AddProperties(
iface,
{
"BarredDialing": _parameters.get("BarredDialing", False),
"CardIdentifier": _parameters.get("CardIdentifier", new_iccid(self)),
"FixedDialing": _parameters.get("FixedDialing", False),
"LockedPins": _parameters.get("LockedPins", dbus.Array([], signature="s")),
"MobileCountryCode": _parameters.get("MobileCountryCode", "310"),
"MobileNetworkCode": _parameters.get("MobileNetworkCode", "150"),
"PreferredLanguages": _parameters.get("PreferredLanguages", ["en"]),
"Present": _parameters.get("Present", dbus.Boolean(True)),
"Retries": _parameters.get("Retries", dbus.Dictionary([["pin", dbus.Byte(3)], ["puk", dbus.Byte(10)]])),
"PinRequired": _parameters.get("PinRequired", "none"),
"SubscriberNumbers": _parameters.get("SubscriberNumbers", ["123456789", "234567890"]),
"SubscriberIdentity": _parameters.get("SubscriberIdentity", new_imsi(self)),
},
)
mock.AddMethods(
iface,
[
("GetProperties", "", "a{sv}", f'ret = self.GetAll("{iface}")'),
(
"SetProperty",
"sv",
"",
f'self.Set("{iface}", args[0], args[1]); '
f'self.EmitSignal("{iface}", "PropertyChanged", "sv", [args[0], args[1]])',
),
("ChangePin", "sss", "", ""),
(
"EnterPin",
"ss",
"",
'correctPin = "1234"\n'
f'iface = "{iface}"\n'
'newRetries = self.Get(iface, "Retries")\n'
'if args[0] == "pin" and args[1] != correctPin:\n'
' newRetries["pin"] = dbus.Byte(newRetries["pin"] - 1)\n'
'elif args[0] == "pin":\n'
' newRetries["pin"] = dbus.Byte(3)\n'
'self.Set(iface, "Retries", newRetries)\n'
'self.EmitSignal(iface, "PropertyChanged", "sv", ["Retries", newRetries])\n'
'if args[0] == "pin" and args[1] != correctPin:\n'
" class Failed(dbus.exceptions.DBusException):\n"
' _dbus_error_name = "org.ofono.Error.Failed"\n'
' raise Failed("Operation failed")',
),
(
"ResetPin",
"sss",
"",
'correctPuk = "12345678"\n'
f'iface = "{iface}"\n'
'newRetries = self.Get(iface, "Retries")\n'
'if args[0] == "puk" and args[1] != correctPuk:\n'
' newRetries["puk"] = dbus.Byte(newRetries["puk"] - 1)\n'
'elif args[0] == "puk":\n'
' newRetries["pin"] = dbus.Byte(3)\n'
' newRetries["puk"] = dbus.Byte(10)\n'
'self.Set(iface, "Retries", newRetries)\n'
'self.EmitSignal(iface, "PropertyChanged", "sv", ["Retries", newRetries])\n'
'if args[0] == "puk" and args[1] != correctPuk:\n'
" class Failed(dbus.exceptions.DBusException):\n"
' _dbus_error_name = "org.ofono.Error.Failed"\n'
' raise Failed("Operation failed")',
),
("LockPin", "ss", "", ""),
("UnlockPin", "ss", "", ""),
],
)
# interface org.ofono.ConnectionManager {
# methods:
# GetProperties(out a{sv} properties);
# SetProperty(in s property,
# in v value);
# AddContext(in s type,
# out o path);
# RemoveContext(in o path);
# DeactivateAll();
# GetContexts(out a(oa{sv}) contexts_with_properties);
# signals:
# PropertyChanged(s name,
# v value);
# ContextAdded(o path,
# v properties);
# ContextRemoved(o path);
# };
def add_connectionmanager_api(mock):
"""Add org.ofono.ConnectionManager API to a mock"""
iface = "org.ofono.ConnectionManager"
mock.AddProperties(
iface,
{
"Attached": _parameters.get("Attached", True),
"Bearer": _parameters.get("Bearer", "gprs"),
"RoamingAllowed": _parameters.get("RoamingAllowed", False),
"Powered": _parameters.get("ConnectionPowered", True),
},
)
mock.AddMethods(
iface,
[
("GetProperties", "", "a{sv}", f'ret = self.GetAll("{iface}")'),
(
"SetProperty",
"sv",
"",
f'self.Set("{iface}", args[0], args[1]); '
f'self.EmitSignal("{iface}", "PropertyChanged", "sv", [args[0], args[1]])',
),
("AddContext", "s", "o", 'ret = "/"'),
("RemoveContext", "o", "", ""),
("DeactivateAll", "", "", ""),
("GetContexts", "", "a(oa{sv})", "ret = dbus.Array([])"),
],
)
# unimplemented Modem object interfaces:
#
# interface org.ofono.NetworkTime {
# methods:
# GetNetworkTime(out a{sv} time);
# signals:
# NetworkTimeChanged(a{sv} time);
# properties:
# };
# interface org.ofono.MessageManager {
# methods:
# GetProperties(out a{sv} properties);
# SetProperty(in s property,
# in v value);
# SendMessage(in s to,
# in s text,
# out o path);
# GetMessages(out a(oa{sv}) messages);
# signals:
# PropertyChanged(s name,
# v value);
# IncomingMessage(s message,
# a{sv} info);
# ImmediateMessage(s message,
# a{sv} info);
# MessageAdded(o path,
# a{sv} properties);
# MessageRemoved(o path);
# };
# interface org.ofono.CallVolume {
# methods:
# GetProperties(out a{sv} properties);
# SetProperty(in s property,
# in v value);
# signals:
# PropertyChanged(s property,
# v value);
# };
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/polkitd.py 0000644 0001751 0001751 00000006773 15071377476 022343 0 ustar 00runner runner """polkitd mock template
This creates the expected methods and properties of the main
org.freedesktop.PolicyKit1 object. By default, all actions are rejected. You
can call AllowUnknown() and SetAllowed() on the mock D-Bus interface to control
which actions are allowed.
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2013-2021 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import time
import dbus
from dbusmock import MOCK_IFACE
BUS_NAME = "org.freedesktop.PolicyKit1"
MAIN_OBJ = "/org/freedesktop/PolicyKit1/Authority"
MAIN_IFACE = "org.freedesktop.PolicyKit1.Authority"
SYSTEM_BUS = True
def load(mock, _parameters):
mock.AddProperties(
MAIN_IFACE,
dbus.Dictionary(
{
"BackendName": "local",
"BackendVersion": "0.8.15",
"BackendFeatures": dbus.UInt32(1),
},
signature="sv",
),
)
# default state
mock.allow_unknown = False
mock.allowed = []
mock.delay = 0
mock.simulate_hang = False
mock.hanging_actions = []
mock.hanging_calls = []
@dbus.service.method(
MAIN_IFACE, in_signature="(sa{sv})sa{ss}us", out_signature="(bba{ss})", async_callbacks=("ok_cb", "_err_cb")
)
def CheckAuthorization(self, _subject, action_id, _details, _flags, _cancellation_id, ok_cb, _err_cb):
time.sleep(self.delay)
allowed = action_id in self.allowed or self.allow_unknown
ret = (allowed, False, {"test": "test"})
if self.simulate_hang or action_id in self.hanging_actions:
self.hanging_calls.append((ok_cb, ret))
else:
ok_cb(ret)
@dbus.service.method(MAIN_IFACE, in_signature="(sa{sv})ss")
def RegisterAuthenticationAgent(_self, _subject, _locale, _object_path):
pass
@dbus.service.method(MOCK_IFACE, in_signature="b", out_signature="")
def AllowUnknown(self, default):
"""Control whether unknown actions are allowed
This controls the return value of CheckAuthorization for actions which were
not explicitly allowed by SetAllowed().
"""
self.allow_unknown = default
@dbus.service.method(MOCK_IFACE, in_signature="as", out_signature="")
def SetAllowed(self, actions):
"""Set allowed actions"""
self.allowed = actions
@dbus.service.method(MOCK_IFACE, in_signature="d", out_signature="")
def SetDelay(self, delay):
"""Makes the CheckAuthorization() method to delay"""
self.delay = delay
@dbus.service.method(MOCK_IFACE, in_signature="b", out_signature="")
def SimulateHang(self, hang):
"""Makes the CheckAuthorization() method to hang"""
self.simulate_hang = hang
@dbus.service.method(MOCK_IFACE, in_signature="as", out_signature="")
def SimulateHangActions(self, actions):
"""Makes the CheckAuthorization() method to hang on such actions"""
self.hanging_actions = actions
@dbus.service.method(MOCK_IFACE, in_signature="", out_signature="")
def ReleaseHangingCalls(self):
"""Calls all the hanging callbacks"""
for cb, ret in self.hanging_calls:
cb(ret)
self.hanging_calls = []
@dbus.service.method(MOCK_IFACE, in_signature="", out_signature="b")
def HaveHangingCalls(self):
"""Check if we've hangling calls"""
return len(self.hanging_calls)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/power_profiles_daemon.py 0000644 0001751 0001751 00000005433 15071377476 025247 0 ustar 00runner runner """power-profiles-daemon < 0.20 mock template
This creates the expected methods and properties of the main
net.hadess.PowerProfiles object.
This provides only the non-deprecated D-Bus API as of version 0.9.
Note that this template is deprecated: Version 0.20 listens on a different
bus name/object path, it is provided in upower_power_profiles_daemon.py
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Bastien Nocera"
__copyright__ = """
(c) 2021, Red Hat Inc.
(c) 2017 - 2022 Martin Pitt
"""
import dbus
BUS_NAME = "net.hadess.PowerProfiles"
MAIN_OBJ = "/net/hadess/PowerProfiles"
MAIN_IFACE = "net.hadess.PowerProfiles"
SYSTEM_BUS = True
def hold_profile(self, profile, reason, application_id):
self.cookie += 1
element = {"Profile": profile, "Reason": reason, "ApplicationId": application_id}
self.holds[self.cookie] = element
self.props[MAIN_IFACE]["ActiveProfileHolds"] = []
for value in self.holds.values():
self.props[MAIN_IFACE]["ActiveProfileHolds"].append(value)
return self.cookie
def release_profile(self, cookie):
self.holds.pop(cookie)
self.props[MAIN_IFACE]["ActiveProfileHolds"] = []
for value in self.holds.values():
self.props[MAIN_IFACE]["ActiveProfileHolds"].append(value)
if len(self.props[MAIN_IFACE]["ActiveProfileHolds"]) == 0:
self.props[MAIN_IFACE]["ActiveProfileHolds"] = dbus.Array([], signature="(aa{sv})")
def load(mock, parameters):
# Loaded!
mock.loaded = True
mock.cookie = 0
mock.hold_profile = hold_profile
mock.release_profile = release_profile
mock.holds = {}
props = {
"ActiveProfile": parameters.get("ActiveProfile", "balanced"),
"PerformanceDegraded": parameters.get("PerformanceDegraded", ""),
"Profiles": [
dbus.Dictionary({"Profile": "power-saver", "Driver": "dbusmock"}, signature="sv"),
dbus.Dictionary({"Profile": "balanced", "Driver": "dbusmock"}, signature="sv"),
dbus.Dictionary({"Profile": "performance", "Driver": "dbusmock"}, signature="sv"),
],
"Actions": dbus.Array([], signature="s"),
"ActiveProfileHolds": dbus.Array([], signature="(aa{sv})"),
}
mock.AddProperties(MAIN_IFACE, dbus.Dictionary(props, signature="sv"))
mock.AddMethods(
MAIN_IFACE,
[
("HoldProfile", "sss", "u", "ret = self.hold_profile(self, args[0], args[1], args[2])"),
("ReleaseProfile", "u", "", "self.release_profile(self, args[0])"),
],
)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/systemd.py 0000644 0001751 0001751 00000005737 15071377476 022364 0 ustar 00runner runner """systemd mock template"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Jonas Ådahl"
__copyright__ = """
(c) 2021 Red Hat
(c) 2017 - 2022 Martin Pitt
"""
import dbus
from gi.repository import GLib
from dbusmock import MOCK_IFACE, mockobject
BUS_PREFIX = "org.freedesktop.systemd1"
PATH_PREFIX = "/org/freedesktop/systemd1"
BUS_NAME = BUS_PREFIX
MAIN_OBJ = PATH_PREFIX
MAIN_IFACE = BUS_PREFIX + ".Manager"
UNIT_IFACE = BUS_PREFIX + ".Unit"
SYSTEM_BUS = True
def load(mock, _parameters):
mock.next_job_id = 1
mock.units = {}
mock.AddProperties(MAIN_IFACE, {"Version": "v246"})
def escape_unit_name(name):
for s in [".", "-"]:
name = name.replace(s, "_")
return name
def emit_job_new_remove(mock, job_id, job_path, name):
mock.EmitSignal(MAIN_IFACE, "JobNew", "uos", [job_id, job_path, name])
mock.EmitSignal(MAIN_IFACE, "JobRemoved", "uoss", [job_id, job_path, name, "done"])
@dbus.service.method(MAIN_IFACE, in_signature="ss", out_signature="o")
def StartUnit(self, name, _mode):
job_id = self.next_job_id
self.next_job_id += 1
job_path = f"{PATH_PREFIX}/Job/{job_id}"
GLib.idle_add(lambda: emit_job_new_remove(self, job_id, job_path, name))
unit_path = self.units[str(name)]
unit = mockobject.objects[unit_path]
unit.UpdateProperties(UNIT_IFACE, {"ActiveState": "active"})
return job_path
@dbus.service.method(MAIN_IFACE, in_signature="ssa(sv)a(sa(sv))", out_signature="o")
def StartTransientUnit(self, name, _mode, _properties, _aux):
job_id = self.next_job_id
self.next_job_id += 1
job_path = f"{PATH_PREFIX}/Job/{job_id}"
GLib.idle_add(lambda: emit_job_new_remove(self, job_id, job_path, name))
return job_path
@dbus.service.method(MAIN_IFACE, in_signature="ss", out_signature="o")
def StopUnit(self, name, _mode):
job_id = self.next_job_id
self.next_job_id += 1
job_path = f"{PATH_PREFIX}/Job/{job_id}"
GLib.idle_add(lambda: emit_job_new_remove(self, job_id, job_path, name))
unit_path = self.units[str(name)]
unit = mockobject.objects[unit_path]
unit.UpdateProperties(UNIT_IFACE, {"ActiveState": "inactive"})
return job_path
@dbus.service.method(MAIN_IFACE, in_signature="s", out_signature="o")
def GetUnit(self, name):
return self.units[str(name)]
@dbus.service.method(MOCK_IFACE, in_signature="s")
def AddMockUnit(self, name):
unit_path = f"{PATH_PREFIX}/unit/{escape_unit_name(name)}"
self.units[str(name)] = unit_path
self.AddObject(
unit_path,
UNIT_IFACE,
{
"Id": name,
"Names": [name],
"LoadState": "loaded",
"ActiveState": "inactive",
},
[],
)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/timedated.py 0000644 0001751 0001751 00000003407 15071377476 022624 0 ustar 00runner runner """systemd timedated mock template
This creates the expected methods and properties of the main
org.freedesktop.timedate object. You can specify D-Bus property values like
"Timezone" or "NTP" in "parameters".
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Iain Lane"
__copyright__ = """
(c) 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import dbus
BUS_NAME = "org.freedesktop.timedate1"
MAIN_OBJ = "/org/freedesktop/timedate1"
MAIN_IFACE = "org.freedesktop.timedate1"
SYSTEM_BUS = True
def setProperty(prop):
return f'self.Set("{MAIN_IFACE}", "{prop}", args[0])'
def load(mock, parameters):
mock.AddMethods(
MAIN_IFACE,
[
# There's nothing this can usefully do, but provide it for compatibility
("SetTime", "xbb", "", ""),
("SetTimezone", "sb", "", setProperty("Timezone")),
("SetLocalRTC", "bbb", "", setProperty("LocalRTC")),
("SetNTP", "bb", "", setProperty("NTP") + "; " + setProperty("NTPSynchronized")),
],
)
mock.AddProperties(
MAIN_IFACE,
dbus.Dictionary(
{
"Timezone": parameters.get("Timezone", "Etc/Utc"),
"LocalRTC": parameters.get("LocalRTC", False),
"NTP": parameters.get("NTP", True),
"NTPSynchronized": parameters.get("NTP", True),
"CanNTP": parameters.get("CanNTP", True),
},
signature="sv",
),
)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/upower.py 0000644 0001751 0001751 00000020454 15071377476 022206 0 ustar 00runner runner """upowerd mock template
This creates the expected methods and properties of the main
org.freedesktop.UPower object, but no devices. You can specify any property
such as 'OnLowBattery' or the return value of 'SuspendAllowed',
'HibernateAllowed', and 'GetCriticalAction' in "parameters".
This provides the 1.0 D-Bus API of upower.
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012, 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import dbus
import dbusmock
from dbusmock import MOCK_IFACE, mockobject
BUS_NAME = "org.freedesktop.UPower"
MAIN_OBJ = "/org/freedesktop/UPower"
MAIN_IFACE = "org.freedesktop.UPower"
SYSTEM_BUS = True
DEVICE_IFACE = "org.freedesktop.UPower.Device"
def load(mock, parameters):
mock.AddMethods(
MAIN_IFACE,
[
(
"EnumerateDevices",
"",
"ao",
'ret = [k for k in objects.keys() if "/devices" in k and not k.endswith("/DisplayDevice")]',
),
],
)
props = dbus.Dictionary(
{
"DaemonVersion": parameters.get("DaemonVersion", "0.99"),
"OnBattery": parameters.get("OnBattery", False),
"LidIsPresent": parameters.get("LidIsPresent", True),
"LidIsClosed": parameters.get("LidIsClosed", False),
"LidForceSleep": parameters.get("LidForceSleep", True),
"IsDocked": parameters.get("IsDocked", False),
},
signature="sv",
)
mock.AddMethods(
MAIN_IFACE,
[
("GetCriticalAction", "", "s", f'ret = "{parameters.get("GetCriticalAction", "HybridSleep")}"'),
("GetDisplayDevice", "", "o", 'ret = "/org/freedesktop/UPower/devices/DisplayDevice"'),
],
)
mock.p_display_dev = "/org/freedesktop/UPower/devices/DisplayDevice"
# add Display device; for defined properties, see
# http://cgit.freedesktop.org/upower/tree/src/org.freedesktop.UPower.xml
mock.AddObject(
mock.p_display_dev,
DEVICE_IFACE,
{
"Type": dbus.UInt32(0),
"State": dbus.UInt32(0),
"Percentage": dbus.Double(0.0),
"Energy": dbus.Double(0.0),
"EnergyFull": dbus.Double(0.0),
"EnergyRate": dbus.Double(0.0),
"TimeToEmpty": dbus.Int64(0),
"TimeToFull": dbus.Int64(0),
"IsPresent": dbus.Boolean(False),
"IconName": dbus.String(""),
# LEVEL_NONE
"WarningLevel": dbus.UInt32(1),
},
[
("Refresh", "", "", ""),
],
)
mock.AddProperties(MAIN_IFACE, props)
@dbus.service.method(MOCK_IFACE, in_signature="ss", out_signature="s")
def AddAC(self, device_name, model_name):
"""Convenience method to add an AC object
You have to specify a device name which must be a valid part of an object
path, e. g. "mock_ac", and an arbitrary model name.
Please note that this does not set any global properties such as
"on-battery".
Returns the new object path.
"""
path = "/org/freedesktop/UPower/devices/" + device_name
self.AddObject(
path,
DEVICE_IFACE,
{
"PowerSupply": dbus.Boolean(True),
"Model": dbus.String(model_name),
"Online": dbus.Boolean(True),
},
[],
)
self.EmitSignal(MAIN_IFACE, "DeviceAdded", "o", [path])
return path
@dbus.service.method(MOCK_IFACE, in_signature="ssdx", out_signature="s")
def AddDischargingBattery(self, device_name, model_name, percentage, seconds_to_empty):
"""Convenience method to add a discharging battery object
You have to specify a device name which must be a valid part of an object
path, e. g. "mock_ac", an arbitrary model name, the charge percentage, and
the seconds until the battery is empty.
Please note that this does not set any global properties such as
"on-battery".
Returns the new object path.
"""
path = "/org/freedesktop/UPower/devices/" + device_name
self.AddObject(
path,
DEVICE_IFACE,
{
"PowerSupply": dbus.Boolean(True),
"IsPresent": dbus.Boolean(True),
"Model": dbus.String(model_name),
"Percentage": dbus.Double(percentage),
"TimeToEmpty": dbus.Int64(seconds_to_empty),
"EnergyFull": dbus.Double(100.0),
"Energy": dbus.Double(percentage),
# UP_DEVICE_STATE_DISCHARGING
"State": dbus.UInt32(2),
# UP_DEVICE_KIND_BATTERY
"Type": dbus.UInt32(2),
},
[],
)
self.EmitSignal(MAIN_IFACE, "DeviceAdded", "o", [path])
return path
@dbus.service.method(MOCK_IFACE, in_signature="ssdx", out_signature="s")
def AddChargingBattery(self, device_name, model_name, percentage, seconds_to_full):
"""Convenience method to add a charging battery object
You have to specify a device name which must be a valid part of an object
path, e. g. "mock_ac", an arbitrary model name, the charge percentage, and
the seconds until the battery is full.
Please note that this does not set any global properties such as
"on-battery".
Returns the new object path.
"""
path = "/org/freedesktop/UPower/devices/" + device_name
self.AddObject(
path,
DEVICE_IFACE,
{
"PowerSupply": dbus.Boolean(True),
"IsPresent": dbus.Boolean(True),
"Model": dbus.String(model_name),
"Percentage": dbus.Double(percentage),
"TimeToFull": dbus.Int64(seconds_to_full),
"EnergyFull": dbus.Double(100.0),
"Energy": dbus.Double(percentage),
# UP_DEVICE_STATE_CHARGING
"State": dbus.UInt32(1),
# UP_DEVICE_KIND_BATTERY
"Type": dbus.UInt32(2),
},
[],
)
self.EmitSignal(MAIN_IFACE, "DeviceAdded", "o", [path])
return path
@dbus.service.method(MOCK_IFACE, in_signature="uuddddxxbsu", out_signature="")
def SetupDisplayDevice(
self,
_type, # noqa: RUF052, RUF100 (access to local dummy variable); but this is API now
state,
percentage,
energy,
energy_full,
energy_rate,
time_to_empty,
time_to_full,
is_present,
icon_name,
warning_level,
):
"""Convenience method to configure DisplayDevice properties
This calls Set() for all properties that the DisplayDevice is defined to
have, and is shorter if you have to completely set it up instead of
changing just one or two properties.
"""
display_props = mockobject.objects[self.p_display_dev]
display_props.Set(DEVICE_IFACE, "Type", dbus.UInt32(_type))
display_props.Set(DEVICE_IFACE, "State", dbus.UInt32(state))
display_props.Set(DEVICE_IFACE, "Percentage", percentage)
display_props.Set(DEVICE_IFACE, "Energy", energy)
display_props.Set(DEVICE_IFACE, "EnergyFull", energy_full)
display_props.Set(DEVICE_IFACE, "EnergyRate", energy_rate)
display_props.Set(DEVICE_IFACE, "TimeToEmpty", dbus.Int64(time_to_empty))
display_props.Set(DEVICE_IFACE, "TimeToFull", dbus.Int64(time_to_full))
display_props.Set(DEVICE_IFACE, "IsPresent", is_present)
display_props.Set(DEVICE_IFACE, "IconName", icon_name)
display_props.Set(DEVICE_IFACE, "WarningLevel", dbus.UInt32(warning_level))
@dbus.service.method(MOCK_IFACE, in_signature="oa{sv}", out_signature="")
def SetDeviceProperties(_self, object_path, properties):
"""Convenience method to Set a device's properties.
object_path: the device to update
properties: dictionary of keys to dbus variants.
Changing this property will trigger the device's PropertiesChanged signal.
"""
device = dbusmock.get_object(object_path)
# set the properties
for key, value in properties.items():
device.Set(DEVICE_IFACE, key, value)
@dbus.service.method(MOCK_IFACE, in_signature="o", out_signature="")
def RemoveDevice(self, device_path):
self.RemoveObject(device_path)
self.EmitSignal(MAIN_IFACE, "DeviceRemoved", "o", [device_path])
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/upower_power_profiles_daemon.py 0000644 0001751 0001751 00000005246 15071377476 026652 0 ustar 00runner runner """power-profiles-daemon >= 0.20 mock template
This creates the expected methods and properties of the main
org.freedesktop.UPower.PowerProfiles object.
This provides the D-Bus API as of version 0.20.
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Bastien Nocera"
__copyright__ = """
(c) 2021, Red Hat Inc.
(c) 2017 - 2024 Martin Pitt
"""
import dbus
BUS_NAME = "org.freedesktop.UPower.PowerProfiles"
MAIN_OBJ = "/org/freedesktop/UPower/PowerProfiles"
MAIN_IFACE = "org.freedesktop.UPower.PowerProfiles"
SYSTEM_BUS = True
def hold_profile(self, profile, reason, application_id):
self.cookie += 1
element = {"Profile": profile, "Reason": reason, "ApplicationId": application_id}
self.holds[self.cookie] = element
self.props[MAIN_IFACE]["ActiveProfileHolds"] = []
for value in self.holds.values():
self.props[MAIN_IFACE]["ActiveProfileHolds"].append(value)
return self.cookie
def release_profile(self, cookie):
self.holds.pop(cookie)
self.props[MAIN_IFACE]["ActiveProfileHolds"] = []
for value in self.holds.values():
self.props[MAIN_IFACE]["ActiveProfileHolds"].append(value)
if len(self.props[MAIN_IFACE]["ActiveProfileHolds"]) == 0:
self.props[MAIN_IFACE]["ActiveProfileHolds"] = dbus.Array([], signature="(aa{sv})")
def load(mock, parameters):
# Loaded!
mock.loaded = True
mock.cookie = 0
mock.hold_profile = hold_profile
mock.release_profile = release_profile
mock.holds = {}
props = {
"ActiveProfile": parameters.get("ActiveProfile", "balanced"),
"PerformanceDegraded": parameters.get("PerformanceDegraded", ""),
"Profiles": [
dbus.Dictionary({"Profile": "power-saver", "Driver": "dbusmock"}, signature="sv"),
dbus.Dictionary({"Profile": "balanced", "Driver": "dbusmock"}, signature="sv"),
dbus.Dictionary({"Profile": "performance", "Driver": "dbusmock"}, signature="sv"),
],
"Actions": dbus.Array([], signature="s"),
"ActiveProfileHolds": dbus.Array([], signature="(aa{sv})"),
}
mock.AddProperties(MAIN_IFACE, dbus.Dictionary(props, signature="sv"))
mock.AddMethods(
MAIN_IFACE,
[
("HoldProfile", "sss", "u", "ret = self.hold_profile(self, args[0], args[1], args[2])"),
("ReleaseProfile", "u", "", "self.release_profile(self, args[0])"),
],
)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/templates/urfkill.py 0000644 0001751 0001751 00000007161 15071377476 022335 0 ustar 00runner runner """urfkill mock template
This creates the expected methods and properties of the main
urfkill object, but no devices. You can specify any property
such as urfkill in "parameters".
"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Jussi Pakkanen"
__copyright__ = """
(C) 2015 Canonical ltd
(c) 2017 - 2022 Martin Pitt
"""
import dbus
import dbusmock
SYSTEM_BUS = True
BUS_NAME = "org.freedesktop.URfkill"
MAIN_OBJ = "/org/freedesktop/URfkill"
MAIN_IFACE = "org.freedesktop.URfkill"
individual_objects = ["BLUETOOTH", "FM", "GPS", "NFC", "UWB", "WIMAX", "WLAN", "WWAN"]
type2objectname = {
1: "WLAN",
2: "BLUETOOTH",
3: "UWB",
4: "WIMAX",
5: "WWAN",
6: "GPS",
7: "FM",
}
KS_NOTAVAILABLE = -1
KS_UNBLOCKED = 0
KS_SOFTBLOCKED = 1
KS_HARDBLOCKED = 2
def toggle_flight_mode(self, new_block_state):
new_block_state = bool(new_block_state)
if self.flight_mode == new_block_state:
return True
self.flight_mode = new_block_state
for i in individual_objects:
old_value = self.internal_states[i]
if old_value == 1:
continue # It was already blocked so we don't need to do anything
path = "/org/freedesktop/URfkill/" + i
obj = dbusmock.get_object(path)
if new_block_state:
obj.Set("org.freedesktop.URfkill.Killswitch", "state", 1)
obj.EmitSignal("org.freedesktop.URfkill.Killswitch", "StateChanged", "", [])
else:
obj.Set("org.freedesktop.URfkill.Killswitch", "state", 0)
obj.EmitSignal("org.freedesktop.URfkill.Killswitch", "StateChanged", "", [])
self.EmitSignal(MAIN_IFACE, "FlightModeChanged", "b", [self.flight_mode])
return True
def block(self, index, should_block):
should_block = bool(should_block)
if index not in type2objectname:
return False
objname = type2objectname[index]
new_block_state = 1 if should_block else 0
if self.internal_states[objname] != new_block_state:
path = "/org/freedesktop/URfkill/" + objname
obj = dbusmock.get_object(path)
self.internal_states[objname] = new_block_state
obj.Set("org.freedesktop.URfkill.Killswitch", "state", new_block_state)
obj.EmitSignal("org.freedesktop.URfkill.Killswitch", "StateChanged", "", [])
return True
def load(mock, parameters):
mock.toggle_flight_mode = toggle_flight_mode
mock.block = block
mock.flight_mode = False
mock.internal_states = {}
for oname in individual_objects:
mock.internal_states[oname] = KS_UNBLOCKED
# First we create the main urfkill object.
mock.AddMethods(
MAIN_IFACE,
[
("IsFlightMode", "", "b", "ret = self.flight_mode"),
("FlightMode", "b", "b", "ret = self.toggle_flight_mode(self, args[0])"),
("Block", "ub", "b", "ret = self.block(self, args[0], args[1])"),
],
)
mock.AddProperties(
MAIN_IFACE,
dbus.Dictionary(
{
"DaemonVersion": parameters.get("DaemonVersion", "0.6.0"),
"KeyControl": parameters.get("KeyControl", True),
},
signature="sv",
),
)
for i in individual_objects:
path = "/org/freedesktop/URfkill/" + i
mock.AddObject(path, "org.freedesktop.URfkill.Killswitch", {"state": mock.internal_states[i]}, [])
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/dbusmock/testcase.py 0000644 0001751 0001751 00000051365 15071377476 020507 0 ustar 00runner runner """unittest.TestCase convenience methods for DBusMocks"""
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import enum
import os
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
from pathlib import Path
from typing import Any, Dict, Optional, Tuple
import dbus
import dbus.proxies
from dbusmock.mockobject import MOCK_IFACE, OBJECT_MANAGER_IFACE, load_module
class BusType(enum.Enum):
"""Represents a system or session bus"""
SESSION = "session"
SYSTEM = "system"
@property
def environ(self) -> Tuple[str, Optional[str]]:
"""Returns the name and value of this bus' address environment variable"""
env = f"DBUS_{self.value.upper()}_BUS_ADDRESS"
value = os.environ.get(env)
return env, value
def get_connection(self) -> dbus.bus.Connection:
"""Get a dbus.bus.BusConnection() object to this bus.
This uses the current environment variables for this bus (if any) and falls back
to dbus.SystemBus() or dbus.SessionBus() otherwise.
This is preferrable to dbus.SystemBus() and dbus.SessionBus() as those
do not get along with multiple changing local test buses.
"""
_, val = self.environ
if val:
return dbus.bus.BusConnection(val)
if self == BusType.SYSTEM:
return dbus.SystemBus()
return dbus.SessionBus()
def reload_configuration(self):
"""Notify this bus that it needs to reload the configuration"""
bus = self.get_connection()
dbus_obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
dbus_if = dbus.Interface(dbus_obj, "org.freedesktop.DBus")
dbus_if.ReloadConfig()
def wait_for_bus_object(self, dest: str, path: str, timeout: float = 60.0):
"""Wait for an object to appear on D-Bus
Raise an exception if object does not appear within one minute. You can
change the timeout in seconds with the "timeout" keyword argument.
"""
bus = self.get_connection()
last_exc = None
# we check whether the name is owned first, to avoid race conditions
# with service activation; once it's owned, wait until we can actually
# call methods
while timeout > 0:
if bus.name_has_owner(dest):
try:
p = dbus.Interface(bus.get_object(dest, path), dbus_interface=dbus.INTROSPECTABLE_IFACE)
p.Introspect()
break
except dbus.exceptions.DBusException as e:
last_exc = e
if ".UnknownInterface" in str(e):
break
timeout -= 0.1
time.sleep(0.1)
if timeout <= 0:
assert timeout > 0, f"timed out waiting for D-Bus object {path}: {last_exc}"
class PrivateDBus:
"""A D-Bus daemon instance that represents a private session or system bus.
If used as a context manager it will automatically start the bus and clean up
after itself on exit:
>>> with PrivateDBus(BusType.SESSION) as bus:
>>> do_something(bus)
Otherwise, `start()` and `stop()` manually.
"""
def __init__(self, bustype: BusType):
self.bustype = bustype
self._daemon: Optional[subprocess.Popen] = None
self._datadir = Path(tempfile.mkdtemp(prefix="dbusmock_data_"))
self._socket = self._datadir / f"{self.bustype.value}_bus.socket"
subdir = "system-services" if bustype == BusType.SYSTEM else "services"
self._servicedir = self._datadir / subdir
self._servicedir.mkdir(parents=True)
self._config = self._servicedir / f"dbusmock_{self.bustype.value}_cfg"
self._config.write_text(
f"""
{self.bustype.value}
unix:path={self._socket}
{self._servicedir}
"""
)
def __enter__(self) -> "PrivateDBus":
# Allow for start() to be called manually even before the `with`
if self._daemon is None:
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# Allow for stop() to be called manually within `with`
if self._daemon is not None:
self.stop()
@property
def address(self) -> str:
"""Returns this D-Bus' address in the environment variable format, i.e. something like
unix:path=/path/to/socket
"""
assert self._daemon is not None, "Call start() first"
return f"unix:path={self._socket}"
@property
def servicedir(self) -> Path:
"""The services directory (full path) for any ``.service`` files that need to be known to
this D-Bus.
"""
return self._servicedir
@property
def pid(self) -> int:
"""Return the pid of this D-Bus daemon process"""
assert self._daemon is not None, "Call start() first"
return self._daemon.pid
def start(self):
"""Start the D-Bus daemon"""
argv = ["dbus-daemon", f"--config-file={self._config}", "--print-pid=1"]
# pylint: disable=consider-using-with
self._daemon = subprocess.Popen(argv, stdout=subprocess.PIPE)
# we don't need the value, but waiting for it ensures that the bus has
# started up and is listening
assert self._daemon.stdout is not None
pid = self._daemon.stdout.readline().strip()
assert int(pid) == self._daemon.pid, "dbus-daemon pid mismatch"
self._daemon.stdout.close()
assert self._socket.exists(), "D-Bus socket not created"
env, _ = self.bustype.environ
os.environ[env] = self.address
def stop(self):
"""Stop the D-Bus daemon"""
if self._daemon:
try:
self._daemon.terminate()
try:
self._daemon.wait(timeout=1)
except subprocess.TimeoutExpired:
self._daemon.kill()
except ProcessLookupError:
pass
self._daemon = None
shutil.rmtree(self._datadir, ignore_errors=True)
def enable_service(self, service: str):
"""Enable the given well-known service name inside dbusmock
This symlinks a service file from the usual dbus service directories
into the dbusmock environment. Doing that allows the service to be
launched automatically if they are defined within $XDG_DATA_DIRS.
The daemon configuration is reloaded if a test bus is running.
"""
xdg_data_dirs = os.environ.get("XDG_DATA_DIRS") or "/usr/local/share/:/usr/share/"
subdir = "system-services" if self.bustype == BusType.SYSTEM else "services"
for d in xdg_data_dirs.split(":"):
src = Path(d) / "dbus-1" / subdir / f"{service}.service"
if src.exists():
assert self._servicedir.exists()
(self._servicedir / f"{service}.service").symlink_to(src)
break
else:
raise AssertionError(f"Service {service} not found in XDG_DATA_DIRS ({xdg_data_dirs})")
if self._daemon:
self.bustype.reload_configuration()
def disable_service(self, service):
"""Disable the given well known service name inside dbusmock
This unlink's the .service file for the service and reloads the
daemon configuration if a test bus is running.
"""
try:
(self._servicedir / f"{service}.service").unlink()
except OSError:
raise AssertionError(f"Service {service} not found") from None
if self._daemon:
self.bustype.reload_configuration()
class DBusTestCase(unittest.TestCase):
"""Base class for D-Bus mock tests.
This provides some convenience API to start/stop local D-Buses, so that you
can run a private local session and/or system bus to run mocks on.
This also provides a spawn_server() static method to run the D-Bus mock
server in a separate process.
"""
session_bus_pid = None
system_bus_pid = None
_DBusTestCase__datadir = None
_busses: Dict[BusType, PrivateDBus] = {
BusType.SESSION: None, # type: ignore[dict-item]
BusType.SYSTEM: None, # type: ignore[dict-item]
}
@staticmethod
def _bus(bustype: BusType) -> PrivateDBus:
"""Return (and create if necessary) the singleton DBus for the given bus type"""
if not DBusTestCase._busses.get(bustype):
DBusTestCase._busses[bustype] = PrivateDBus(bustype)
return DBusTestCase._busses[bustype]
@staticmethod
def get_services_dir(system_bus: bool = False) -> str:
"""Returns the private services directory for the bus type in question.
This allows dropping in a .service file so that the dbus server inside
dbusmock can launch it.
"""
bus = DBusTestCase._bus(bustype=BusType.SYSTEM if system_bus else BusType.SESSION)
return str(bus.servicedir)
@classmethod
def tearDownClass(cls):
for bustype in BusType:
bus = DBusTestCase._busses.get(bustype)
if bus:
bus.stop()
setattr(DBusTestCase, f"{bustype.value}_bus_pid", None)
del DBusTestCase._busses[bustype]
@classmethod
def __start_bus(cls, bus_type) -> None:
bustype = BusType(bus_type)
old_pid = getattr(DBusTestCase, f"{bustype.value}_bus_pid")
assert old_pid is None, f"PID {old_pid} still alive?"
assert DBusTestCase._busses.get(bustype) is None
bus = DBusTestCase._bus(bustype)
bus.start()
setattr(DBusTestCase, f"{bustype.value}_bus_pid", bus.pid)
@classmethod
def start_session_bus(cls) -> None:
"""Set up a private local session bus
This gets stopped automatically at class teardown.
"""
cls.__start_bus("session")
@classmethod
def start_system_bus(cls) -> None:
"""Set up a private local system bus
This gets stopped automatically at class teardown.
"""
cls.__start_bus("system")
@staticmethod
def get_dbus(system_bus: bool = False) -> dbus.Bus:
"""Get a dbus.bus.BusConnection() object to this bus
This is preferrable to dbus.SystemBus() and dbus.SessionBus() as those
do not get along with multiple changing local test buses.
This is a legacy method kept for backwards compatibility, use
BusType.get_connection() instead.
"""
bustype = BusType.SYSTEM if system_bus else BusType.SESSION
return bustype.get_connection()
@staticmethod
def wait_for_bus_object(dest: str, path: str, system_bus: bool = False, timeout: int = 600):
"""Wait for an object to appear on D-Bus
Raise an exception if object does not appear within one minute. You can
change the timeout with the "timeout" keyword argument which specifies
deciseconds.
This is a legacy method kept for backwards compatibility, use
BusType.wait_for_bus_object() instead.
"""
bustype = BusType.SYSTEM if system_bus else BusType.SESSION
bustype.wait_for_bus_object(dest, path, timeout / 10.0)
@staticmethod
def spawn_server(name: str, path: str, interface: str, system_bus: bool = False, stdout=None) -> subprocess.Popen:
"""Run a DBusMockObject instance in a separate process
The daemon will terminate automatically when the D-Bus that it connects
to goes down. If that does not happen (e. g. you test on the actual
system/session bus), you need to kill it manually.
This function blocks until the spawned DBusMockObject is ready and
listening on the bus.
Returns the Popen object of the spawned daemon.
This is a legacy method kept for backwards compatibility,
use SpawnedMock.spawn_for_name() instead.
"""
bustype = BusType.SYSTEM if system_bus else BusType.SESSION
server = SpawnedMock.spawn_for_name(name, path, interface, bustype, stdout=stdout, stderr=None)
return server.process
@staticmethod
def spawn_server_template(
template: str, parameters: Optional[Dict[str, Any]] = None, stdout=None, system_bus: Optional[bool] = None
) -> Tuple[subprocess.Popen, dbus.proxies.ProxyObject]:
"""Run a D-Bus mock template instance in a separate process
This starts a D-Bus mock process and loads the given template with
(optional) parameters into it. For details about templates see
dbusmock.DBusMockObject.AddTemplate().
Usually a template should specify SYSTEM_BUS = False/True to select whether it
gets loaded on the session or system bus. This can be overridden with the system_bus
parameter. For templates which don't set SYSTEM_BUS, this parameter has to be set.
The daemon will terminate automatically when the D-Bus that it connects
to goes down. If that does not happen (e. g. you test on the actual
system/session bus), you need to kill it manually.
This function blocks until the spawned DBusMockObject is ready and
listening on the bus.
Returns a pair (daemon Popen object, main dbus object).
This is a legacy method kept for backwards compatibility,
use SpawnedMock.spawn_with_template() instead.
"""
if system_bus is not None: # noqa: SIM108
bustype = BusType.SYSTEM if system_bus else BusType.SESSION
else:
bustype = None
server = SpawnedMock.spawn_with_template(template, parameters, bustype, stdout, stderr=None)
return server.process, server.obj
@staticmethod
def enable_service(service, system_bus: bool = False) -> None:
"""Enable the given well known service name inside dbusmock
This symlinks a service file from the usual dbus service directories
into the dbusmock environment. Doing that allows the service to be
launched automatically if they are defined within $XDG_DATA_DIRS.
The daemon configuration is reloaded if a test bus is running.
This is a legacy method kept for backwards compatibility. Use
PrivateDBus.enable_service() instead.
"""
bustype = BusType.SYSTEM if system_bus else BusType.SESSION
bus = DBusTestCase._bus(bustype)
bus.enable_service(service)
@staticmethod
def disable_service(service, system_bus: bool = False) -> None:
"""Disable the given well known service name inside dbusmock
This unlink's the .service file for the service and reloads the
daemon configuration if a test bus is running.
"""
bustype = BusType.SYSTEM if system_bus else BusType.SESSION
bus = DBusTestCase._bus(bustype)
bus.disable_service(service)
class SpawnedMock:
"""
An instance of a D-Bus mock template instance in a separate process.
See SpawnedMock.spawn_for_name() and SpawnedMock.spawn_with_template()
the typical entry points.
"""
def __init__(self, process: subprocess.Popen, obj: dbus.proxies.ProxyObject):
self._process = process
self._process_is_running = True
self._obj = obj
@property
def process(self) -> subprocess.Popen:
"""Returns the process that is this mock template"""
return self._process
@property
def obj(self):
"""The D-Bus object this server was spawned for"""
return self._obj
def __enter__(self) -> "SpawnedMock":
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.terminate()
def terminate(self):
"""Terminate the process"""
if self._process.returncode is None:
self._process.poll()
if self._process.returncode is None:
if self._process.stdout:
self._process.stdout.close()
if self._process.stderr:
self._process.stderr.close()
try:
self._process.terminate()
try:
self._process.wait(timeout=1)
except subprocess.TimeoutExpired:
self._process.kill()
except ProcessLookupError:
pass
@property
def stdout(self):
"""
The stdout of the process, if no caller-specific stdout
was specified in spawn_for_name() or spawn_with_template().
"""
return self._process.stdout
@property
def stderr(self):
"""
The stderr of the process, if no caller-specific stderr
was specified in spawn_for_name() or spawn_with_template().
"""
return self._process.stderr
@classmethod
def spawn_for_name(
cls,
name: str,
path: str,
interface: str,
bustype: BusType = BusType.SESSION,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) -> "SpawnedMock":
"""Run a DBusMockObject instance in a separate process
The daemon will terminate automatically when the D-Bus that it connects
to goes down. If that does not happen (e. g. you test on the actual
system/session bus), you need to kill it manually.
This function blocks until the spawned DBusMockObject is ready and
listening on the bus.
By default, stdout and stderr of the spawned process is available via the
SpawnedMock.stdout and SpawnedMock.stderr properties on the returned object.
"""
argv = [sys.executable, "-m", "dbusmock", f"--{bustype.value}", name, path, interface]
bus = bustype.get_connection()
if bus.name_has_owner(name):
raise AssertionError(f"Trying to spawn a server for name {name} but it is already owned!")
# pylint: disable=consider-using-with
daemon = subprocess.Popen(argv, stdout=stdout, stderr=stderr)
# wait for daemon to start up
bustype.wait_for_bus_object(name, path)
obj = bus.get_object(name, path)
return cls(process=daemon, obj=obj)
@classmethod
def spawn_with_template(
cls,
template: str,
parameters: Optional[Dict[str, Any]] = None,
bustype: Optional[BusType] = None,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
):
"""Run a D-Bus mock template instance in a separate process
This starts a D-Bus mock process and loads the given template with
(optional) parameters into it. For details about templates see
dbusmock.DBusMockObject.AddTemplate().
Usually a template should specify SYSTEM_BUS = False/True to select whether it
gets loaded on the session or system bus. This can be overridden with the system_bus
parameter. For templates which don't set SYSTEM_BUS, this parameter has to be set.
The daemon will terminate automatically when the D-Bus that it connects
to goes down. If that does not happen (e. g. you test on the actual
system/session bus), you need to kill it manually.
This function blocks until the spawned DBusMockObject is ready and
listening on the bus.
Returns a pair (daemon Popen object, main dbus object).
"""
# we need the bus address from the template module
module = load_module(template)
is_object_manager = module.IS_OBJECT_MANAGER if hasattr(module, "IS_OBJECT_MANAGER") else False
if is_object_manager and not hasattr(module, "MAIN_IFACE"): # noqa: SIM108
interface_name = OBJECT_MANAGER_IFACE
else:
interface_name = module.MAIN_IFACE
if bustype is None:
bustype = BusType.SYSTEM if module.SYSTEM_BUS else BusType.SESSION
assert bustype is not None
server = SpawnedMock.spawn_for_name(module.BUS_NAME, module.MAIN_OBJ, interface_name, bustype, stdout, stderr)
if not parameters:
parameters = dbus.Dictionary({}, signature="sv")
server.obj.AddTemplate(template, parameters, dbus_interface=MOCK_IFACE)
return server
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9873736
python_dbusmock-0.37.1/distro/ 0000755 0001751 0001751 00000000000 15071377700 015773 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/distro/python-dbusmock.spec 0000644 0001751 0001751 00000002673 15071377476 022017 0 ustar 00runner runner %global modname dbusmock
Name: python-%{modname}
Version: 0.23.0
Release: 1%{?dist}
Summary: Mock D-Bus objects
License: LGPL-3.0-or-later
URL: https://pypi.python.org/pypi/python-dbusmock
Source0: https://files.pythonhosted.org/packages/source/p/%{name}/python_%{modname}-%{version}.tar.gz
BuildArch: noarch
BuildRequires: git
BuildRequires: python3-devel
BuildRequires: python3-gobject
BuildRequires: python3-pytest
BuildRequires: dbus-x11
BuildRequires: upower
%global _description\
With this program/Python library you can easily create mock objects on\
D-Bus. This is useful for writing tests for software which talks to\
D-Bus services such as upower, systemd, ConsoleKit, gnome-session or\
others, and it is hard (or impossible without root privileges) to set\
the state of the real services to what you expect in your tests.
%description %_description
%package -n python3-dbusmock
Summary: %summary (Python3)
Requires: python3-dbus, python3-gobject, dbus-x11
%description -n python3-dbusmock %_description
%prep
%autosetup -n python_%{modname}-%{version}
rm -rf python-%{modname}.egg-info
%generate_buildrequires
%pyproject_buildrequires
%build
%pyproject_wheel
%install
%pyproject_install
%pyproject_save_files -l dbusmock
%check
%{__python3} -m unittest -v
%files -n python3-dbusmock -f %{pyproject_files}
%doc README.md COPYING
%changelog
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9873736
python_dbusmock-0.37.1/doc/ 0000755 0001751 0001751 00000000000 15071377700 015234 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/doc/conf.py 0000644 0001751 0001751 00000003104 15071377476 016543 0 ustar 00runner runner # Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "python-dbusmock"
copyright = "2023 - 2025, Martin Pitt" # noqa: A001
author = "Martin Pitt"
try:
# created by setuptools_scm
from dbusmock._version import __version__ as release
except ImportError:
release = "0.git"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.viewcode",
"sphinx.ext.intersphinx",
"sphinx.ext.napoleon",
"myst_parser",
"autoapi.extension",
]
templates_path = ["_templates"]
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
apidoc_module_dir = "../dbusmock"
apidoc_output_dir = "."
apidoc_separate_modules = True
apidoc_excluded_paths = ["tests"]
autoapi_dirs = ["../dbusmock"]
autoapi_type = "python"
autoapi_member_order = "bysource"
autoapi_options = [
"members",
"undoc-members",
"show-inheritance",
"show-module-summary",
"special-members",
"imported-members",
]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/doc/index.rst 0000644 0001751 0001751 00000000736 15071377476 017115 0 ustar 00runner runner .. python-dbusmock documentation master file, created by
sphinx-quickstart on Mon Nov 6 19:11:55 2023.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
.. include:: ../README.md
:parser: myst_parser.sphinx_
.. toctree::
:maxdepth: 2
:caption: Contents:
API Documentation
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/packit.yaml 0000644 0001751 0001751 00000002702 15071377476 016641 0 ustar 00runner runner # See the documentation for more information:
# https://packit.dev/docs/configuration/
upstream_project_url: https://github.com/martinpitt/python-dbusmock
# HACK: should work without, but propose_downstream fails; https://github.com/packit/packit-service/issues/1511
specfile_path: distro/python-dbusmock.spec
issue_repository: https://github.com/martinpitt/python-dbusmock
copy_upstream_release_description: true
upstream_package_name: python-dbusmock
downstream_package_name: python-dbusmock
actions:
create-archive:
- python3 -m build --sdist
# spec and tarball need to be in the same directory
- sh -c 'mv dist/*.tar.* distro; ls distro/python_dbusmock-*.tar.*'
srpm_build_deps:
- python3-build
- python3-setuptools_scm
- python3dist(wheel)
jobs:
- job: copr_build
trigger: pull_request
targets:
- fedora-latest-i386
- fedora-latest-aarch64
- fedora-latest-ppc64le
- fedora-latest-s390x
- fedora-latest-armhfp
# needed for tests
- fedora-all
- centos-stream-10-x86_64
- job: tests
trigger: pull_request
targets:
- fedora-all
- centos-stream-10-x86_64
- job: propose_downstream
trigger: release
dist_git_branches:
- fedora-all
- job: koji_build
trigger: commit
dist_git_branches:
- fedora-all
- job: bodhi_update
trigger: commit
dist_git_branches:
# rawhide updates are created automatically
- fedora-branched
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9873736
python_dbusmock-0.37.1/plans/ 0000755 0001751 0001751 00000000000 15071377700 015604 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/plans/all.fmf 0000644 0001751 0001751 00000000110 15071377476 017050 0 ustar 00runner runner summary:
Run all tests
discover:
how: fmf
execute:
how: tmt
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/pyproject.toml 0000644 0001751 0001751 00000005346 15071377476 017425 0 ustar 00runner runner [build-system]
requires = ["setuptools >= 45", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[project]
name = "python-dbusmock"
description = "Mock D-Bus objects"
readme = "README.md"
license = { file = "COPYING" }
authors = [
{ name = "Martin Pitt", email = "martin@piware.de" },
]
classifiers = [
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
"Programming Language :: Python :: 3",
"Development Status :: 6 - Mature",
"Operating System :: POSIX :: Linux",
"Operating System :: POSIX :: BSD",
"Operating System :: Unix",
"Topic :: Software Development :: Quality Assurance",
"Topic :: Software Development :: Testing",
"Topic :: Software Development :: Testing :: Mocking",
"Topic :: Software Development :: Testing :: Unit",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dynamic = ["version"]
requires-python = ">=3.8"
dependencies = ["dbus-python"]
[project.urls]
homepage = "https://github.com/martinpitt/python-dbusmock"
[tool.setuptools]
packages = ["dbusmock", "dbusmock.templates"]
[tool.setuptools_scm]
write_to = "dbusmock/_version.py"
write_to_template = """
__version__ = "{version}"
"""
version_scheme = 'post-release'
[tool.pylint]
format = { max-line-length = 130 }
"messages control" = { disable = ["invalid-name", "too-many-arguments", "too-many-positional-arguments"] }
design = { max-args = 7, max-locals = 25, max-public-methods = 25 }
[tool.mypy]
warn_unused_configs = true
[[tool.mypy.overrides]]
module = ["dbus.*", "gi.repository", "dbusmock._version"]
ignore_missing_imports = true
[tool.ruff]
line-length = 130
preview = true
[tool.ruff.lint]
select = [
"A", # flake8-builtins
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"E", # pycodestyle
"EXE", # flake8-executable
"F", # pyflakes
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"PIE", # unnecessary type wrappers
"PLE", # pylint errors
"PGH", # pygrep-hooks
"PYI", # https://pypi.org/project/flake8-pyi/ type hints
"RET", # flake8-return
"RSE", # flake8-raise
"RUF", # ruff rules
"SIM", # flake8-simplify
"T10", # flake8-debugger
"TCH", # flake8-type-checking
"UP", # pyupgrade, e.g. f-string checks
"W", # warnings (mostly whitespace)
"YTT", # flake8-2020
]
ignore = [
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
"SIM105", # Use `contextlib.suppress(KeyError)` instead of `try`-`except`-`pass`
]
[tool.black]
line-length = 118
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9933736
python_dbusmock-0.37.1/python_dbusmock.egg-info/ 0000755 0001751 0001751 00000000000 15071377700 021371 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903679.0
python_dbusmock-0.37.1/python_dbusmock.egg-info/PKG-INFO 0000644 0001751 0001751 00000062676 15071377677 022524 0 ustar 00runner runner Metadata-Version: 2.4
Name: python-dbusmock
Version: 0.37.1
Summary: Mock D-Bus objects
Author-email: Martin Pitt
License: GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
Project-URL: homepage, https://github.com/martinpitt/python-dbusmock
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Development Status :: 6 - Mature
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: POSIX :: BSD
Classifier: Operating System :: Unix
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Software Development :: Testing :: Mocking
Classifier: Topic :: Software Development :: Testing :: Unit
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: COPYING
Requires-Dist: dbus-python
Dynamic: license-file

python-dbusmock
===============
## Purpose
With this program/Python library you can easily create mock objects on
D-Bus. This is useful for writing tests for software which talks to
D-Bus services such as upower, systemd, logind, gnome-session or others,
and it is hard (or impossible without root privileges) to set the state
of the real services to what you expect in your tests.
Suppose you want to write tests for gnome-settings-daemon's power
plugin, or another program that talks to upower. You want to verify that
after the configured idle time the program suspends the machine. So your
program calls `org.freedesktop.UPower.Suspend()` on the system D-Bus.
Now, your test suite should not really talk to the actual system D-Bus
and the real upower; a `make check` that suspends your machine will not
be considered very friendly by most people, and if you want to run this
in continuous integration test servers or package build environments,
chances are that your process does not have the privilege to suspend, or
there is no system bus or upower to begin with. Likewise, there is no
way for an user process to forcefully set the system/seat idle flag in
logind, so your tests cannot set up the expected test environment on the
real daemon.
That's where mock objects come into play: They look like the real API
(or at least the parts that you actually need), but they do not actually
do anything (or only some action that you specify yourself). You can
configure their state, behaviour and responses as you like in your test,
without making any assumptions about the real system status.
When using a local system/session bus, you can do unit or integration
testing without needing root privileges or disturbing a running system.
The Python API offers some convenience functions like
`start_session_bus()` and `start_system_bus()` for this, in a
`DBusTestCase` class (subclass of the standard `unittest.TestCase`) or
alternatively as a `@pytest.fixture`.
You can use this with any programming language, as you can run the
mocker as a normal program. The actual setup of the mock (adding
objects, methods, properties, and signals) all happen via D-Bus methods
on the `org.freedesktop.DBus.Mock` interface. You just don't have the
convenience D-Bus launch API that way.
## Simple example using Python's unittest
Picking up the above example about mocking upower's `Suspend()` method,
this is how you would set up a mock upower in your test case:
```python
import subprocess
import dbus
import dbusmock
class TestMyProgram(dbusmock.DBusTestCase):
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(system_bus=True)
def setUp(self):
self.p_mock = self.spawn_server('org.freedesktop.UPower',
'/org/freedesktop/UPower',
'org.freedesktop.UPower',
system_bus=True,
stdout=subprocess.PIPE)
# Get a proxy for the UPower object's Mock interface
self.dbus_upower_mock = dbus.Interface(self.dbus_con.get_object(
'org.freedesktop.UPower', '/org/freedesktop/UPower'),
dbusmock.MOCK_IFACE)
self.dbus_upower_mock.AddMethod('', 'Suspend', '', '', '')
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_suspend_on_idle(self):
# run your program in a way that should trigger one suspend call
# now check the log that we got one Suspend() call
self.assertRegex(self.p_mock.stdout.readline(), b'^[0-9.]+ Suspend$')
```
Let's walk through:
- We derive our tests from `dbusmock.DBusTestCase` instead of
`unittest.TestCase` directly, to make use of the convenience API
to start a local system bus.
- `setUpClass()` starts a local system bus, and makes a connection
to it available to all methods as `dbus_con`. `True` means that we
connect to the system bus, not the session bus. We can use the
same bus for all tests, so doing this once in `setUpClass()`
instead of `setUp()` is enough.
- `setUp()` spawns the mock D-Bus server process for an initial
`/org/freedesktop/UPower` object with an `org.freedesktop.UPower`
D-Bus interface on the system bus. We capture its stdout to be
able to verify that methods were called.
We then call `org.freedesktop.DBus.Mock.AddMethod()` to add a
`Suspend()` method to our new object to the default D-Bus
interface. This will not do anything (except log its call to
stdout). It takes no input arguments, returns nothing, and does
not run any custom code.
- `tearDown()` stops our mock D-Bus server again. We do this so that
each test case has a fresh and clean upower instance, but of
course you can also set up everything in `setUpClass()` if tests
do not interfere with each other on setting up the mock.
- `test_suspend_on_idle()` is the actual test case. It needs to run
your program in a way that should trigger one suspend call. Your
program will try to call `Suspend()`, but as that's now being
served by our mock instead of upower, there will not be any actual
machine suspend. Our mock process will log the method call
together with a time stamp; you can use the latter for doing
timing related tests, but we just ignore it here.
## Simple example using pytest
The same functionality as above but instead using the pytest fixture provided
by this package.
```python
import subprocess
import dbus
import pytest
import dbusmock
@pytest.fixture
def upower_mock(dbusmock_system):
p_mock = dbusmock_system.spawn_server(
'org.freedesktop.UPower',
'/org/freedesktop/UPower',
'org.freedesktop.UPower',
system_bus=True,
stdout=subprocess.PIPE)
# Get a proxy for the UPower object's Mock interface
dbus_upower_mock = dbus.Interface(dbusmock_system.get_dbus(True).get_object(
'org.freedesktop.UPower',
'/org/freedesktop/UPower'
), dbusmock.MOCK_IFACE)
dbus_upower_mock.AddMethod('', 'Suspend', '', '', '')
yield p_mock
p_mock.stdout.close()
p_mock.terminate()
p_mock.wait()
def test_suspend_on_idle(upower_mock):
# run your program in a way that should trigger one suspend call
# now check the log that we got one Suspend() call
assert upower_mock.stdout.readline() == b'^[0-9.]+ Suspend$'
```
Let's walk through:
- We import the `dbusmock_system` fixture from dbusmock which provides us
with a system bus started for our test case wherever the
`dbusmock_system` argument is used by a test case and/or a pytest
fixture.
- The `upower_mock` fixture spawns the mock D-Bus server process for an initial
`/org/freedesktop/UPower` object with an `org.freedesktop.UPower`
D-Bus interface on the system bus. We capture its stdout to be
able to verify that methods were called.
We then call `org.freedesktop.DBus.Mock.AddMethod()` to add a
`Suspend()` method to our new object to the default D-Bus
interface. This will not do anything (except log its call to
stdout). It takes no input arguments, returns nothing, and does
not run any custom code.
This mock server process is yielded to the test function that uses
the `upower_mock` fixture - once the test is complete the process is
terminated again.
- `test_suspend_on_idle()` is the actual test case. It needs to run
your program in a way that should trigger one suspend call. Your
program will try to call `Suspend()`, but as that's now being
served by our mock instead of upower, there will not be any actual
machine suspend. Our mock process will log the method call
together with a time stamp; you can use the latter for doing
timing related tests, but we just ignore it here.
## Simple example from shell
We use the actual session bus for this example. You can use
`dbus-run-session` to start a private one as well if you want, but that
is not part of the actual mocking.
So let's start a mock at the D-Bus name `com.example.Foo` with an
initial "main" object on path /, with the main D-Bus interface
`com.example.Foo.Manager`:
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager
On another terminal, let's first see what it does:
gdbus introspect --session -d com.example.Foo -o /
You'll see that it supports the standard D-Bus `Introspectable` and
`Properties` interfaces, as well as the `org.freedesktop.DBus.Mock`
interface for controlling the mock, but no "real" functionality yet.
So let's add a method:
gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod '' Ping '' '' ''
Now you can see the new method in `introspect`, and call it:
gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Ping
The mock process in the other terminal will log the method call with a
time stamp, and you'll see something like `1348832614.970 Ping`.
Now add another method with two int arguments and a return value and
call it:
gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod \
'' Add 'ii' 'i' 'ret = args[0] + args[1]'
gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Add 2 3
This will print `(5,)` as expected (remember that the return value is
always a tuple), and again the mock process will log the Add method
call.
You can do the same operations in e. g. d-feet or any other D-Bus
language binding.
## Interactive debugging
It's possible to use dbus-mock to run interactive sessions using something like:
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager -e $SHELL
Where a shell session with the defined mocks is set and others can be added.
Or more complex ones such as:
python3 -m dbusmock --session -t upower -e \
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager -e \
gdbus introspect --session -d com.example.Foo -o /
## Logging
Usually you want to verify which methods have been called on the mock
with which arguments. There are three ways to do that:
- By default, the mock process writes the call log to stdout.
- You can call the mock process with the `-l`/`--logfile` argument,
or specify a log file object in the `spawn_server()` method if you
are using Python.
- You can use the `GetCalls()`, `GetMethodCalls()` and
`ClearCalls()` methods on the `org.freedesktop.DBus.Mock` D-Bus
interface to get an array of tuples describing the calls.
## Templates
Some D-Bus services are commonly used in test suites, such as UPower or
NetworkManager. python-dbusmock provides "templates" which set up the
common structure of these services (their main objects, properties, and
methods) so that you do not need to carry around this common code, and
only need to set up the particular properties and specific D-Bus objects
that you need. These templates can be parameterized for common
customizations, and they can provide additional convenience methods on
the `org.freedesktop.DBus.Mock` interface to provide more abstract
functionality like "add a battery".
For example, for starting a server with the `upower` template in
Python you can run
(self.p_mock, self.obj_upower) = self.spawn_server_template(
'upower', {'OnBattery': True}, stdout=subprocess.PIPE)
or load a template into an already running server with the
`AddTemplate()` method; this is particularly useful if you are not using
Python:
python3 -m dbusmock --system org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower
gdbus call --system -d org.freedesktop.UPower -o /org/freedesktop/UPower -m org.freedesktop.DBus.Mock.AddTemplate 'upower' '{"OnBattery": }'
This creates all expected properties such as `DaemonVersion`, and
changes the default for one of them (`OnBattery`) through the (optional)
parameters dict.
If you do not need to specify parameters, you can do this in a simpler
way with
python3 -m dbusmock --template upower
The template does not create any devices by default. You can add some
with the template's convenience methods like
ac_path = self.dbusmock.AddAC('mock_AC', 'Mock AC')
bt_path = self.dbusmock.AddChargingBattery('mock_BAT', 'Mock Battery', 30.0, 1200)
or calling `AddObject()` yourself with the desired properties, of
course.
Templates commonly implement some non-trivial functionality with actual Python
methods and the standard [dbus-python](https://dbus.freedesktop.org/doc/dbus-python/)
[`@dbus.service.method`](https://dbus.freedesktop.org/doc/dbus-python/dbus.service.html#dbus.service.method)
decorator.
To build your own template, you can copy
[dbusmock/templates/SKELETON](./dbusmock/templates/SKELETON) to your
new template file name and replace `CHANGEME` with the actual code/values.
Look at [dbusmock/templates/upower.py](./dbusmock/templates/upower.py) for
a real-life implementation.
A template can be loaded from these locations:
* Provide a path to its `.py` file. This is intended for running tests out of
git/build trees with very project specific or unstable templates.
* From [`$XDG_DATA_DIRS/python-dbusmock/templates/`*name*`.py`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
This is intended for shipping reusable templates in distribution development
packages. Load them by module name.
* python-dbusmock [ships a set of widely applicable templates](./dbusmock/templates/)
which are collaboratively maintained, like the `upower` one in the example
above. Load them by module name.
## More Examples
Have a look at the test suite for two real-live use cases:
- `tests/test_upower.py` simulates upowerd, in a more complete way
than in above example and using the `upower` template. It verifies
that `upower --dump` is convinced that it's talking to upower.
- `tests/test_api.py` runs a mock on the session bus and exercises
all available functionality, such as adding additional objects,
properties, multiple methods, input arguments, return values, code
in methods, sending signals, raising exceptions, and introspection.
## Documentation
The `dbusmock` module has extensive documentation built in, which you
can read with e. g. `pydoc3 dbusmock` or online at
https://martinpitt.github.io/python-dbusmock/
`pydoc3 dbusmock.DBusMockObject` shows the D-Bus API of the mock object,
i. e. methods like `AddObject()`, `AddMethod()` etc. which are used to
set up your mock object.
`pydoc3 dbusmock.DBusTestCase` shows the convenience Python API for
writing test cases with local private session/system buses and launching
the server.
`pydoc3 dbusmock.templates` shows all available templates.
`pydoc3 dbusmock.templates.NAME` shows the documentation and available
parameters for the `NAME` template.
`python3 -m dbusmock --help` shows the arguments and options for running
the mock server as a program.
## Development
python-dbusmock is hosted on https://github.com/martinpitt/python-dbusmock
Run the unit tests with `python3 -m unittest` or `pytest`.
In CI, the unit tests run in containers. You can run them locally with e.g.
tests/run registry.fedoraproject.org/fedora:latest
Check the [unit-tests GitHub workflow](.github/workflows/tests.yml) for the
operating systems/container images on which python-dbusmock is tested and
supported.
To debug failures interactively, run
DEBUG=1 tests/run [image]
which will sleep on failures. You can then attach to the running container
image with e.g. `podman exec -itl bash`. The `/source` directory is mounted from the
host, i.e. edit files in your normal git checkout outside of the container, and
re-run all tests in the container shell like above. You can also run a specific
test:
python3 -m unittest tests.test_api.TestAPI.test_onearg_ret
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903679.0
python_dbusmock-0.37.1/python_dbusmock.egg-info/SOURCES.txt 0000644 0001751 0001751 00000003447 15071377677 023302 0 ustar 00runner runner .gitignore
.pre-commit-config.yaml
COPYING
NEWS
README.md
packit.yaml
pyproject.toml
.fmf/version
.github/workflows/pages.yml
.github/workflows/release.yml
.github/workflows/tests.yml
dbusmock/__init__.py
dbusmock/__main__.py
dbusmock/_version.py
dbusmock/mockobject.py
dbusmock/pytest_fixtures.py
dbusmock/testcase.py
dbusmock/templates/SKELETON
dbusmock/templates/__init__.py
dbusmock/templates/bluez5-obex.py
dbusmock/templates/bluez5.py
dbusmock/templates/gnome_screensaver.py
dbusmock/templates/gsd_rfkill.py
dbusmock/templates/logind.py
dbusmock/templates/low_memory_monitor.py
dbusmock/templates/modemmanager.py
dbusmock/templates/networkmanager.py
dbusmock/templates/notification_daemon.py
dbusmock/templates/ofono.py
dbusmock/templates/polkitd.py
dbusmock/templates/power_profiles_daemon.py
dbusmock/templates/systemd.py
dbusmock/templates/timedated.py
dbusmock/templates/upower.py
dbusmock/templates/upower_power_profiles_daemon.py
dbusmock/templates/urfkill.py
distro/python-dbusmock.spec
doc/conf.py
doc/index.rst
plans/all.fmf
python_dbusmock.egg-info/PKG-INFO
python_dbusmock.egg-info/SOURCES.txt
python_dbusmock.egg-info/dependency_links.txt
python_dbusmock.egg-info/requires.txt
python_dbusmock.egg-info/top_level.txt
tests/__init__.py
tests/conftest.py
tests/main.fmf
tests/run
tests/run-centos
tests/run-debian
tests/run-fedora
tests/run-ubuntu
tests/test_api.py
tests/test_api_pytest.py
tests/test_bluez5.py
tests/test_cli.py
tests/test_code.py
tests/test_gnome_screensaver.py
tests/test_gsd_rfkill.py
tests/test_logind.py
tests/test_low_memory_monitor.py
tests/test_modemmanager.py
tests/test_networkmanager.py
tests/test_notification_daemon.py
tests/test_ofono.py
tests/test_polkitd.py
tests/test_power_profiles_daemon.py
tests/test_systemd.py
tests/test_timedated.py
tests/test_upower.py
tests/test_urfkill.py ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903679.0
python_dbusmock-0.37.1/python_dbusmock.egg-info/dependency_links.txt 0000644 0001751 0001751 00000000001 15071377677 025454 0 ustar 00runner runner
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903679.0
python_dbusmock-0.37.1/python_dbusmock.egg-info/requires.txt 0000644 0001751 0001751 00000000014 15071377677 024001 0 ustar 00runner runner dbus-python
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903679.0
python_dbusmock-0.37.1/python_dbusmock.egg-info/top_level.txt 0000644 0001751 0001751 00000000011 15071377677 024130 0 ustar 00runner runner dbusmock
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9943736
python_dbusmock-0.37.1/setup.cfg 0000644 0001751 0001751 00000000046 15071377700 016310 0 ustar 00runner runner [egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1759903679.9933736
python_dbusmock-0.37.1/tests/ 0000755 0001751 0001751 00000000000 15071377700 015631 5 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/__init__.py 0000644 0001751 0001751 00000000000 15071377476 017742 0 ustar 00runner runner ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/conftest.py 0000644 0001751 0001751 00000000054 15071377476 020041 0 ustar 00runner runner pytest_plugins = "dbusmock.pytest_fixtures"
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/main.fmf 0000644 0001751 0001751 00000000745 15071377476 017267 0 ustar 00runner runner summary: smoke test
duration: 1m
require:
- python3-dbusmock
test: |
set -eux -o pipefail
python3 -m dbusmock com.example.Foo / com.example.Foo.Manager &
MOCK=$!
trap "kill $MOCK; wait $MOCK || true" EXIT INT QUIT PIPE
until busctl list --user | grep -q com.example.Foo; do sleep 0.5; done
busctl call --user com.example.Foo / org.freedesktop.DBus.Mock AddMethod sssss '' 'Ping' '' '' ''
busctl introspect --user com.example.Foo / com.example.Foo.Manager | grep Ping
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/run 0000755 0001751 0001751 00000001021 15071377476 016367 0 ustar 00runner runner #!/bin/sh
set -eux
IMAGE="$1"
if type podman >/dev/null 2>&1; then
RUNC=podman
else
RUNC="sudo docker"
fi
# only run static code checks on a single release, too annoying to keep the code compatible with multiple versions
if [ "${IMAGE%fedora:latest}" = "$IMAGE" ]; then
SKIP_STATIC_CHECKS="1"
fi
OS=${IMAGE##*/}
OS=${OS%:*}
$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
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/run-centos 0000644 0001751 0001751 00000001510 15071377476 017660 0 ustar 00runner runner #!/bin/sh
set -eux
# install build dependencies
dnf -y install python3-setuptools python3 python3-gobject-base \
python3-dbus dbus-x11 util-linux \
upower NetworkManager ModemManager bluez libnotify polkit
if ! grep -q :el /etc/os-release; then
dnf -y install power-profiles-daemon python3-pytest
else
dnf -y install python3-pip
pip install pytest
fi
if [ "$SKIP_STATIC_CHECKS" != "1" ]; then
dnf -y install python3-pylint python3-mypy python3-pip black
pip install ruff
fi
# systemd's tools otherwise fail on "not been booted with systemd"
mkdir -p /run/systemd/system
# run build and test as user
useradd build
su -s /bin/sh - build << EOF || { [ -z "$DEBUG" ] || sleep infinity; exit 1; }
set -ex
cd /source
export SKIP_STATIC_CHECKS="$SKIP_STATIC_CHECKS"
python3 -m unittest -v
python3 -m pytest -v
EOF
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/run-debian 0000644 0001751 0001751 00000002602 15071377476 017612 0 ustar 00runner runner #!/bin/sh
set -eux
# go-faster apt
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/90nolanguages
# upgrade
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y eatmydata
eatmydata apt-get -y --purge dist-upgrade
# install build dependencies
eatmydata apt-get install --no-install-recommends -y git \
python3-all python3-setuptools python3-setuptools-scm python3-build python3-venv \
python3-dbus python3-pytest python3-gi gir1.2-glib-2.0 \
dbus libnotify-bin upower network-manager bluez ofono ofono-scripts power-profiles-daemon
# systemd's tools otherwise fail on "not been booted with systemd"
mkdir -p /run/systemd/system
# run build and test as user
useradd build
su -s /bin/sh - build << EOF || { [ -z "$DEBUG" ] || sleep infinity; exit 1; }
set -ex
export SKIP_STATIC_CHECKS="$SKIP_STATIC_CHECKS"
cp -r $(pwd) /tmp/source
cd /tmp/source
python3 -m unittest -v
python3 -m pytest -vv -k 'test_pytest or TestAPI'
# massively parallel test to check for races
for i in \$(seq 100); do
( PYTHONPATH=. python3 tests/test_api.py TestTemplates || touch /tmp/fail ) &
done
wait
[ ! -e /tmp/fail ]
my_version=\$(git describe --abbrev=0)
# test sdist with PyPA build
python3 -m build --sdist
tar --wildcards --strip-components=1 -xvf dist/python_dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
grep "^Version: \${my_version}" PKG-INFO
grep "^## Purpose" PKG-INFO
EOF
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/run-fedora 0000644 0001751 0001751 00000001510 15071377476 017625 0 ustar 00runner runner #!/bin/sh
set -eux
# install build dependencies
dnf -y install python3-setuptools python3 python3-gobject-base \
python3-dbus dbus-x11 util-linux \
upower NetworkManager ModemManager bluez libnotify polkit
if ! grep -q :el /etc/os-release; then
dnf -y install power-profiles-daemon python3-pytest
else
dnf -y install python3-pip
pip install pytest
fi
if [ "$SKIP_STATIC_CHECKS" != "1" ]; then
dnf -y install python3-pylint python3-mypy python3-pip black
pip install ruff
fi
# systemd's tools otherwise fail on "not been booted with systemd"
mkdir -p /run/systemd/system
# run build and test as user
useradd build
su -s /bin/sh - build << EOF || { [ -z "$DEBUG" ] || sleep infinity; exit 1; }
set -ex
cd /source
export SKIP_STATIC_CHECKS="$SKIP_STATIC_CHECKS"
python3 -m unittest -v
python3 -m pytest -v
EOF
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/run-ubuntu 0000644 0001751 0001751 00000002602 15071377476 017712 0 ustar 00runner runner #!/bin/sh
set -eux
# go-faster apt
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/90nolanguages
# upgrade
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y eatmydata
eatmydata apt-get -y --purge dist-upgrade
# install build dependencies
eatmydata apt-get install --no-install-recommends -y git \
python3-all python3-setuptools python3-setuptools-scm python3-build python3-venv \
python3-dbus python3-pytest python3-gi gir1.2-glib-2.0 \
dbus libnotify-bin upower network-manager bluez ofono ofono-scripts power-profiles-daemon
# systemd's tools otherwise fail on "not been booted with systemd"
mkdir -p /run/systemd/system
# run build and test as user
useradd build
su -s /bin/sh - build << EOF || { [ -z "$DEBUG" ] || sleep infinity; exit 1; }
set -ex
export SKIP_STATIC_CHECKS="$SKIP_STATIC_CHECKS"
cp -r $(pwd) /tmp/source
cd /tmp/source
python3 -m unittest -v
python3 -m pytest -vv -k 'test_pytest or TestAPI'
# massively parallel test to check for races
for i in \$(seq 100); do
( PYTHONPATH=. python3 tests/test_api.py TestTemplates || touch /tmp/fail ) &
done
wait
[ ! -e /tmp/fail ]
my_version=\$(git describe --abbrev=0)
# test sdist with PyPA build
python3 -m build --sdist
tar --wildcards --strip-components=1 -xvf dist/python_dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
grep "^Version: \${my_version}" PKG-INFO
grep "^## Purpose" PKG-INFO
EOF
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_api.py 0000644 0001751 0001751 00000131151 15071377476 020027 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import importlib.util
import os
import shutil
import signal
import subprocess
import sys
import tempfile
import time
import tracemalloc
import unittest
from pathlib import Path
import dbus
import dbus.mainloop.glib
from gi.repository import GLib
import dbusmock
tracemalloc.start(25)
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
# "a b" in py2/3 compatible unicode
UNICODE = b"a\xe2\x99\xa5b".decode("UTF-8")
class TestAPI(dbusmock.DBusTestCase):
"""Test dbus-mock API"""
@classmethod
def setUpClass(cls):
cls.start_session_bus()
cls.dbus_con = cls.get_dbus()
def setUp(self):
# pylint: disable=consider-using-with
self.mock_log = tempfile.NamedTemporaryFile() # noqa: SIM115
self.p_mock = self.spawn_server(
"org.freedesktop.Test", "/", "org.freedesktop.Test.Main", stdout=self.mock_log
)
self.obj_test = self.dbus_con.get_object("org.freedesktop.Test", "/")
self.dbus_test = dbus.Interface(self.obj_test, "org.freedesktop.Test.Main")
self.dbus_mock = dbus.Interface(self.obj_test, dbusmock.MOCK_IFACE)
self.dbus_props = dbus.Interface(self.obj_test, dbus.PROPERTIES_IFACE)
def assertLog(self, regex):
self.assertRegex(Path(self.mock_log.name).read_bytes(), regex)
def tearDown(self):
if self.p_mock.stdout:
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_noarg_noret(self):
"""no arguments, no return value"""
self.dbus_mock.AddMethod("", "Do", "", "", "")
self.assertEqual(self.dbus_test.Do(), None)
# check that it's logged correctly
self.assertLog(b"^[0-9.]+ Do$")
def test_onearg_noret(self):
"""one argument, no return value"""
self.dbus_mock.AddMethod("", "Do", "s", "", "")
self.assertEqual(self.dbus_test.Do("Hello"), None)
# check that it's logged correctly
self.assertLog(b'^[0-9.]+ Do "Hello"$')
def test_onearg_ret(self):
"""one argument, code for return value"""
self.dbus_mock.AddMethod("", "Do", "s", "s", "ret = args[0]")
self.assertEqual(self.dbus_test.Do("Hello"), "Hello")
def test_unicode_str(self):
"""unicode string roundtrip"""
self.dbus_mock.AddMethod("", "Do", "s", "s", "ret = args[0] * 2")
self.assertEqual(self.dbus_test.Do(UNICODE), dbus.String(UNICODE * 2))
def test_twoarg_ret(self):
"""two arguments, code for return value"""
self.dbus_mock.AddMethod("", "Do", "si", "s", "ret = args[0] * args[1]")
self.assertEqual(self.dbus_test.Do("foo", 3), "foofoofoo")
# check that it's logged correctly
self.assertLog(b'^[0-9.]+ Do "foo" 3$')
def test_array_arg(self):
"""array argument"""
self.dbus_mock.AddMethod(
"",
"Do",
"iaous",
"",
f"""assert len(args) == 4
assert args[0] == -1;
assert args[1] == ['/foo']
assert type(args[1]) == dbus.Array
assert type(args[1][0]) == dbus.ObjectPath
assert args[2] == 5
assert args[3] == {UNICODE!r}
""",
)
self.assertEqual(self.dbus_test.Do(-1, ["/foo"], 5, UNICODE), None)
# check that it's logged correctly
self.assertLog(b'^[0-9.]+ Do -1 \\["/foo"\\] 5 "a\\xe2\\x99\\xa5b"$')
def test_dict_arg(self):
"""dictionary argument"""
self.dbus_mock.AddMethod(
"",
"Do",
"ia{si}u",
"",
"""assert len(args) == 3
assert args[0] == -1;
assert args[1] == {'foo': 42}
assert type(args[1]) == dbus.Dictionary
assert args[2] == 5
""",
)
self.assertEqual(self.dbus_test.Do(-1, {"foo": 42}, 5), None)
# check that it's logged correctly
self.assertLog(b'^[0-9.]+ Do -1 {"foo": 42} 5$')
def test_multi_output(self):
"""multiple output values"""
self.dbus_mock.AddMethod("", "AddSub", "ii", "ii", "ret = (args[0] + args[1], args[0] - args[1])")
self.assertEqual(self.dbus_test.AddSub(3, 5), (8, -2))
def test_exception(self):
"""raise a D-Bus exception"""
self.dbus_mock.AddMethod(
"", "Do", "", "i", 'raise dbus.exceptions.DBusException("no good", name="com.example.Error.NoGood")'
)
with self.assertRaises(dbus.exceptions.DBusException) as cm:
self.dbus_test.Do()
self.assertEqual(cm.exception.get_dbus_name(), "com.example.Error.NoGood")
self.assertEqual(cm.exception.get_dbus_message(), "no good")
self.assertLog(b"\n[0-9.]+ Do raised: com.example.Error.NoGood:.*\n")
def test_methods_on_other_interfaces(self):
"""methods on other interfaces"""
self.dbus_mock.AddMethod("org.freedesktop.Test.Other", "OtherDo", "", "", "")
self.dbus_mock.AddMethods(
"org.freedesktop.Test.Other", [("OtherDo2", "", "", ""), ("OtherDo3", "i", "i", "ret = args[0]")]
)
# should not be on the main interface
self.assertRaises(dbus.exceptions.DBusException, self.dbus_test.OtherDo)
# should be on the other interface
self.assertEqual(self.obj_test.OtherDo(dbus_interface="org.freedesktop.Test.Other"), None)
self.assertEqual(self.obj_test.OtherDo2(dbus_interface="org.freedesktop.Test.Other"), None)
self.assertEqual(self.obj_test.OtherDo3(42, dbus_interface="org.freedesktop.Test.Other"), 42)
# check that it's logged correctly
self.assertLog(b"^[0-9.]+ OtherDo\n[0-9.]+ OtherDo2\n[0-9.]+ OtherDo3 42$")
def test_methods_same_name(self):
"""methods with same name on different interfaces"""
self.dbus_mock.AddMethod("org.iface1", "Do", "i", "i", "ret = args[0] + 2")
self.dbus_mock.AddMethod("org.iface2", "Do", "i", "i", "ret = args[0] + 3")
# should not be on the main interface
self.assertRaises(dbus.exceptions.DBusException, self.dbus_test.Do)
# should be on the other interface
self.assertEqual(self.obj_test.Do(10, dbus_interface="org.iface1"), 12)
self.assertEqual(self.obj_test.Do(11, dbus_interface="org.iface2"), 14)
# check that it's logged correctly
self.assertLog(b"^[0-9.]+ Do 10\n[0-9.]+ Do 11$")
# now add it to the primary interface, too
self.dbus_mock.AddMethod("", "Do", "i", "i", "ret = args[0] + 1")
self.assertEqual(self.obj_test.Do(9, dbus_interface="org.freedesktop.Test.Main"), 10)
self.assertEqual(self.obj_test.Do(10, dbus_interface="org.iface1"), 12)
self.assertEqual(self.obj_test.Do(11, dbus_interface="org.iface2"), 14)
def test_methods_type_mismatch(self):
"""calling methods with wrong arguments"""
def check(signature, args, err):
self.dbus_mock.AddMethod("", "Do", signature, "", "")
try:
self.dbus_test.Do(*args)
self.fail(f'method call did not raise an error for signature "{signature}" and arguments {args}')
except dbus.exceptions.DBusException as e:
self.assertEqual(e.get_dbus_name(), "org.freedesktop.DBus.Error.InvalidArgs")
self.assertIn(err, str(e))
# not enough arguments
check("i", [], "More items found")
check("is", [1], "More items found")
# too many arguments
check("", [1], "Fewer items found")
check("i", [1, "hello"], "Fewer items found")
# type mismatch
check("u", [-1], "convert negative value to unsigned")
check("i", ["hello"], "dbus.String")
check("i", ["hello"], "integer")
check("s", [1], "Expected a string")
def test_add_object(self):
"""add a new object"""
self.dbus_mock.AddObject(
"/obj1",
"org.freedesktop.Test.Sub",
{
"state": dbus.String("online"),
"cute": dbus.Boolean(True),
},
[],
)
obj1 = self.dbus_con.get_object("org.freedesktop.Test", "/obj1")
dbus_sub = dbus.Interface(obj1, "org.freedesktop.Test.Sub")
dbus_props = dbus.Interface(obj1, dbus.PROPERTIES_IFACE)
# check properties
self.assertEqual(dbus_props.Get("org.freedesktop.Test.Sub", "state"), "online")
self.assertEqual(dbus_props.Get("org.freedesktop.Test.Sub", "cute"), True)
self.assertEqual(dbus_props.GetAll("org.freedesktop.Test.Sub"), {"state": "online", "cute": True})
# add new method
obj1.AddMethod("", "Do", "", "s", 'ret = "hello"', dbus_interface=dbusmock.MOCK_IFACE)
self.assertEqual(dbus_sub.Do(), "hello")
def test_add_object_existing(self):
"""try to add an existing object"""
self.dbus_mock.AddObject("/obj1", "org.freedesktop.Test.Sub", {}, [])
self.assertRaises(
dbus.exceptions.DBusException, self.dbus_mock.AddObject, "/obj1", "org.freedesktop.Test.Sub", {}, []
)
# try to add the main object again
self.assertRaises(
dbus.exceptions.DBusException, self.dbus_mock.AddObject, "/", "org.freedesktop.Test.Other", {}, []
)
def test_add_object_with_methods(self):
"""add a new object with methods"""
self.dbus_mock.AddObject(
"/obj1",
"org.freedesktop.Test.Sub",
{
"state": dbus.String("online"),
"cute": dbus.Boolean(True),
},
[
("Do0", "", "i", "ret = 42"),
("Do1", "i", "i", "ret = 31337"),
],
)
obj1 = self.dbus_con.get_object("org.freedesktop.Test", "/obj1")
self.assertEqual(obj1.Do0(), 42)
self.assertEqual(obj1.Do1(1), 31337)
self.assertRaises(dbus.exceptions.DBusException, obj1.Do2, 31337)
def test_properties(self):
"""add and change properties"""
# no properties by default
self.assertEqual(self.dbus_props.GetAll("org.freedesktop.Test.Main"), {})
# no such property
with self.assertRaises(dbus.exceptions.DBusException) as ctx:
self.dbus_props.Get("org.freedesktop.Test.Main", "version")
self.assertEqual(ctx.exception.get_dbus_name(), "org.freedesktop.Test.Main.UnknownProperty")
self.assertEqual(ctx.exception.get_dbus_message(), "no such property version")
self.assertRaises(
dbus.exceptions.DBusException,
self.dbus_props.Set,
"org.freedesktop.Test.Main",
"version",
dbus.Int32(2),
)
self.dbus_mock.AddProperty("org.freedesktop.Test.Main", "version", dbus.Int32(2))
# once again on default interface
self.dbus_mock.AddProperty("", "connected", dbus.Boolean(True))
self.assertEqual(self.dbus_props.Get("org.freedesktop.Test.Main", "version"), 2)
self.assertEqual(self.dbus_props.Get("org.freedesktop.Test.Main", "connected"), True)
self.assertEqual(self.dbus_props.GetAll("org.freedesktop.Test.Main"), {"version": 2, "connected": True})
with self.assertRaises(dbus.exceptions.DBusException) as ctx:
self.dbus_props.GetAll("org.freedesktop.Test.Bogus")
self.assertEqual(ctx.exception.get_dbus_name(), "org.freedesktop.Test.Main.UnknownInterface")
self.assertEqual(ctx.exception.get_dbus_message(), "no such interface org.freedesktop.Test.Bogus")
# change property
self.dbus_props.Set("org.freedesktop.Test.Main", "version", dbus.Int32(4))
self.assertEqual(self.dbus_props.Get("org.freedesktop.Test.Main", "version"), 4)
# check that the Get/Set calls get logged
log = Path(self.mock_log.name).read_text("UTF-8")
self.assertRegex(log, "\n[0-9.]+ Get / org.freedesktop.Test.Main.version\n")
self.assertRegex(log, "\n[0-9.]+ Get / org.freedesktop.Test.Main.connected\n")
self.assertRegex(log, "\n[0-9.]+ GetAll / org.freedesktop.Test.Main\n")
self.assertRegex(log, "\n[0-9.]+ Set / org.freedesktop.Test.Main.version 4\n")
# add property to different interface
self.dbus_mock.AddProperty("org.freedesktop.Test.Other", "color", dbus.String("yellow"))
self.assertEqual(self.dbus_props.GetAll("org.freedesktop.Test.Main"), {"version": 4, "connected": True})
self.assertEqual(self.dbus_props.GetAll("org.freedesktop.Test.Other"), {"color": "yellow"})
self.assertEqual(self.dbus_props.Get("org.freedesktop.Test.Other", "color"), "yellow")
# add properties with complex types
self.dbus_mock.AddProperty("org.freedesktop.Test.Main", "intarray", dbus.Array([1, 2]))
self.dbus_mock.AddProperty("org.freedesktop.Test.Main", "vardict", dbus.Dictionary({"a": 1}))
self.dbus_mock.AddProperty("org.freedesktop.Test.Main", "struct", dbus.Struct((1, "a")))
changes = [
{
"version": 5,
"connected": False,
},
{
"intarray": [1, 3],
},
{
"vardict": {"b": "2"},
},
{
"struct": (2, "b"),
},
{
"intarray": [3, 7],
"vardict": {"d": dbus.Array([42, 1])},
"struct": (0, "test"),
},
]
changed_props = []
ml = GLib.MainLoop()
def catch(*args, **kwargs):
if kwargs["interface"] != "org.freedesktop.DBus.Properties":
return
self.assertEqual(kwargs["interface"], "org.freedesktop.DBus.Properties")
self.assertEqual(kwargs["member"], "PropertiesChanged")
[iface, changed, _invalidated] = args
self.assertEqual(iface, "org.freedesktop.Test.Main")
changed_props.append(changed)
if len(changed_props) == len(changes):
ml.quit()
match = self.dbus_con.add_signal_receiver(
catch, interface_keyword="interface", path_keyword="path", member_keyword="member"
)
# change property using mock helper
for change in changes:
self.dbus_mock.UpdateProperties("org.freedesktop.Test.Main", change)
self.assertTrue(change.items() <= self.dbus_props.GetAll("org.freedesktop.Test.Main").items())
GLib.timeout_add(3000, ml.quit)
ml.run()
match.remove()
for i, change in enumerate(changes):
self.assertEqual(changed_props[i], change)
# test adding properties with the array type
self.dbus_mock.AddProperty("org.freedesktop.Test.Main", "array", dbus.Array(["first"], signature="s"))
self.assertEqual(self.dbus_props.Get("org.freedesktop.Test.Main", "array"), ["first"])
# test updating properties with the array type
self.dbus_mock.UpdateProperties(
"org.freedesktop.Test.Main", {"array": dbus.Array(["second", "third"], signature="s")}
)
self.assertEqual(self.dbus_props.Get("org.freedesktop.Test.Main", "array"), ["second", "third"])
def test_introspection_methods(self):
"""dynamically added methods appear in introspection"""
dbus_introspect = dbus.Interface(self.obj_test, dbus.INTROSPECTABLE_IFACE)
xml_empty = dbus_introspect.Introspect()
self.assertIn('', xml_empty)
self.assertIn('', xml_empty)
self.dbus_mock.AddMethod("", "Do", "saiv", "i", "ret = 42")
xml_method = dbus_introspect.Introspect()
self.assertNotEqual(xml_empty, xml_method)
self.assertIn('', xml_method)
# various Python versions use different name vs. type ordering
expected1 = """
"""
expected2 = """
"""
self.assertTrue(expected1 in xml_method or expected2 in xml_method, xml_method)
# properties in introspection are not supported by dbus-python right now
def test_introspection_properties(self):
"""dynamically added properties appear in introspection"""
self.dbus_mock.AddProperty("", "Color", "yellow")
self.dbus_mock.AddProperty("org.freedesktop.Test.Sub", "Count", 5)
xml = self.obj_test.Introspect()
self.assertIn('', xml)
self.assertIn('', xml)
# various Python versions use different attribute ordering
self.assertTrue(
'' in xml
or '' in xml,
xml,
)
self.assertTrue(
'' in xml
or '' in xml,
xml,
)
def test_objects_map(self):
"""access global objects map"""
self.dbus_mock.AddMethod("", "EnumObjs", "", "ao", "ret = objects.keys()")
self.assertEqual(self.dbus_test.EnumObjs(), ["/"])
self.dbus_mock.AddObject("/obj1", "org.freedesktop.Test.Sub", {}, [])
self.assertEqual(set(self.dbus_test.EnumObjs()), {"/", "/obj1"})
def test_signals(self):
"""emitting signals"""
self.dbus_mock.AddObject("/obj1", "org.freedesktop.Test.Sub", {}, [])
def do_emit():
self.dbus_mock.EmitSignal("", "SigNoArgs", "", [])
self.dbus_mock.EmitSignal("org.freedesktop.Test.Sub", "SigTwoArgs", "su", ["hello", 42])
self.dbus_mock.EmitSignal(
"org.freedesktop.Test.Sub",
"SigTypeTest",
"iuvao",
[-42, 42, dbus.String("hello", variant_level=1), ["/a", "/b"]],
)
self.dbus_mock.EmitSignalDetailed(
"", "SigDetailed", "su", ["details", 123], {"destination": self.dbus_con.get_unique_name()}
)
self.dbus_mock.EmitSignalDetailed("", "SigDetailedWithPath", "su", ["details", 456], {"path": "/obj1"})
caught = []
ml = GLib.MainLoop()
def catch(*args, **kwargs):
if kwargs["interface"].startswith("org.freedesktop.Test"):
caught.append((args, kwargs))
if len(caught) == 5:
# we caught everything there is to catch, don't wait for the
# timeout
ml.quit()
self.dbus_con.add_signal_receiver(
catch, interface_keyword="interface", path_keyword="path", member_keyword="member"
)
GLib.timeout_add(200, do_emit)
# ensure that the loop quits even when we catch fewer than 2 signals
GLib.timeout_add(3000, ml.quit)
ml.run()
# check SigNoArgs
self.assertEqual(caught[0][0], ())
self.assertEqual(caught[0][1]["member"], "SigNoArgs")
self.assertEqual(caught[0][1]["path"], "/")
self.assertEqual(caught[0][1]["interface"], "org.freedesktop.Test.Main")
# check SigTwoArgs
self.assertEqual(caught[1][0], ("hello", 42))
self.assertEqual(caught[1][1]["member"], "SigTwoArgs")
self.assertEqual(caught[1][1]["path"], "/")
self.assertEqual(caught[1][1]["interface"], "org.freedesktop.Test.Sub")
# check data types in SigTypeTest
self.assertEqual(caught[2][1]["member"], "SigTypeTest")
self.assertEqual(caught[2][1]["path"], "/")
args = caught[2][0]
self.assertEqual(args[0], -42)
self.assertEqual(type(args[0]), dbus.Int32)
self.assertEqual(args[0].variant_level, 0)
self.assertEqual(args[1], 42)
self.assertEqual(type(args[1]), dbus.UInt32)
self.assertEqual(args[1].variant_level, 0)
self.assertEqual(args[2], "hello")
self.assertEqual(type(args[2]), dbus.String)
self.assertEqual(args[2].variant_level, 1)
self.assertEqual(args[3], ["/a", "/b"])
self.assertEqual(type(args[3]), dbus.Array)
self.assertEqual(args[3].variant_level, 0)
self.assertEqual(type(args[3][0]), dbus.ObjectPath)
self.assertEqual(args[3][0].variant_level, 0)
# check SigDetailed
self.assertEqual(caught[3][0], ("details", 123))
self.assertEqual(caught[3][1]["member"], "SigDetailed")
self.assertEqual(caught[3][1]["path"], "/")
self.assertEqual(caught[3][1]["interface"], "org.freedesktop.Test.Main")
self.assertEqual(caught[4][0], ("details", 456))
self.assertEqual(caught[4][1]["member"], "SigDetailedWithPath")
self.assertEqual(caught[4][1]["path"], "/obj1")
self.assertEqual(caught[4][1]["interface"], "org.freedesktop.Test.Main")
# check correct logging
log = Path(self.mock_log.name).read_text("UTF-8")
self.assertRegex(log, "[0-9.]+ emit / org.freedesktop.Test.Main.SigNoArgs\n")
self.assertRegex(log, '[0-9.]+ emit / org.freedesktop.Test.Sub.SigTwoArgs "hello" 42\n')
self.assertRegex(log, "[0-9.]+ emit / org.freedesktop.Test.Sub.SigTypeTest -42 42")
self.assertRegex(log, r'[0-9.]+ emit / org.freedesktop.Test.Sub.SigTypeTest -42 42 "hello" \["/a", "/b"\]\n')
self.assertRegex(log, '[0-9.]+ emit / org.freedesktop.Test.Main.SigDetailed "details" 123\n')
self.assertRegex(log, '[0-9.]+ emit /obj1 org.freedesktop.Test.Main.SigDetailedWithPath "details" 456\n')
def test_signals_type_mismatch(self):
"""emitting signals with wrong arguments"""
def check(signature, args, err):
try:
self.dbus_mock.EmitSignal("", "s", signature, args)
self.fail(f'EmitSignal did not raise an error for signature "{signature}" and arguments {args}')
except dbus.exceptions.DBusException as e:
self.assertEqual(e.get_dbus_name(), "org.freedesktop.DBus.Error.InvalidArgs")
self.assertIn(err, str(e))
# not enough arguments
check("i", [], "More items found")
check("is", [1], "More items found")
# too many arguments
check("", [1], "Fewer items found")
check("i", [1, "hello"], "Fewer items found")
# type mismatch
check("u", [-1], "convert negative value to unsigned")
check("i", ["hello"], "dbus.String")
check("i", ["hello"], "integer")
check("s", [1], "Expected a string")
def test_dbus_get_log(self):
"""query call logs over D-Bus"""
self.assertEqual(self.dbus_mock.ClearCalls(), None)
self.assertEqual(self.dbus_mock.GetCalls(), dbus.Array([]))
self.dbus_mock.AddMethod("", "Do", "", "", "")
self.assertEqual(self.dbus_test.Do(), None)
mock_log = self.dbus_mock.GetCalls()
self.assertEqual(len(mock_log), 1)
self.assertGreater(mock_log[0][0], 10000) # timestamp
self.assertEqual(mock_log[0][1], "Do")
self.assertEqual(mock_log[0][2], [])
self.assertEqual(self.dbus_mock.ClearCalls(), None)
self.assertEqual(self.dbus_mock.GetCalls(), dbus.Array([]))
self.dbus_mock.AddMethod("", "Wop", "s", "s", 'ret="hello"')
self.assertEqual(self.dbus_test.Wop("foo"), "hello")
self.assertEqual(self.dbus_test.Wop("bar"), "hello")
mock_log = self.dbus_mock.GetCalls()
self.assertEqual(len(mock_log), 2)
self.assertGreater(mock_log[0][0], 10000) # timestamp
self.assertEqual(mock_log[0][1], "Wop")
self.assertEqual(mock_log[0][2], ["foo"])
self.assertEqual(mock_log[1][1], "Wop")
self.assertEqual(mock_log[1][2], ["bar"])
self.assertEqual(self.dbus_mock.ClearCalls(), None)
self.assertEqual(self.dbus_mock.GetCalls(), dbus.Array([]))
def test_dbus_get_method_calls(self):
"""query method call logs over D-Bus"""
self.dbus_mock.AddMethod("", "Do", "", "", "")
self.assertEqual(self.dbus_test.Do(), None)
self.assertEqual(self.dbus_test.Do(), None)
self.dbus_mock.AddMethod("", "Wop", "s", "s", 'ret="hello"')
self.assertEqual(self.dbus_test.Wop("foo"), "hello")
self.assertEqual(self.dbus_test.Wop("bar"), "hello")
mock_calls = self.dbus_mock.GetMethodCalls("Do")
self.assertEqual(len(mock_calls), 2)
self.assertEqual(mock_calls[0][1], [])
self.assertEqual(mock_calls[1][1], [])
mock_calls = self.dbus_mock.GetMethodCalls("Wop")
self.assertEqual(len(mock_calls), 2)
self.assertGreater(mock_calls[0][0], 10000) # timestamp
self.assertEqual(mock_calls[0][1], ["foo"])
self.assertGreater(mock_calls[1][0], 10000) # timestamp
self.assertEqual(mock_calls[1][1], ["bar"])
def test_dbus_method_called(self):
"""subscribe to MethodCalled signal"""
loop = GLib.MainLoop()
caught_signals = []
def method_called(method, args, **_):
caught_signals.append((method, args))
loop.quit()
self.dbus_mock.AddMethod("", "Do", "s", "", "")
self.dbus_mock.connect_to_signal("MethodCalled", method_called)
self.assertEqual(self.dbus_test.Do("foo"), None)
GLib.timeout_add(5000, loop.quit)
loop.run()
self.assertEqual(len(caught_signals), 1)
method, args = caught_signals[0]
self.assertEqual(method, "Do")
self.assertEqual(len(args), 1)
self.assertEqual(args[0], "foo")
def test_reset(self):
"""resetting to pristine state"""
self.dbus_mock.AddMethod("", "Do", "", "", "")
self.dbus_mock.AddProperty("", "propone", True)
self.dbus_mock.AddProperty("org.Test.Other", "proptwo", 1)
self.dbus_mock.AddObject("/obj1", "", {}, [])
self.dbus_mock.Reset()
# resets properties and keeps the initial object
self.assertEqual(self.dbus_props.GetAll(""), {})
# resets methods
self.assertRaises(dbus.exceptions.DBusException, self.dbus_test.Do)
# resets other objects
obj1 = self.dbus_con.get_object("org.freedesktop.Test", "/obj1")
self.assertRaises(dbus.exceptions.DBusException, obj1.GetAll, "")
def test_version(self):
self.assertGreater(dbusmock.__version__, "0.28.0")
class TestTemplates(dbusmock.DBusTestCase):
"""Test template API"""
@classmethod
def setUpClass(cls):
cls.start_session_bus()
cls.start_system_bus()
def test_local(self):
"""Load a local template *.py file"""
with tempfile.NamedTemporaryFile(prefix="answer_", suffix=".py") as my_template:
my_template.write(
b"""import dbus
BUS_NAME = 'universe.Ultimate'
MAIN_OBJ = '/'
MAIN_IFACE = 'universe.Ultimate'
SYSTEM_BUS = False
def load(mock, parameters):
mock.AddMethods(MAIN_IFACE, [('Answer', 's', 'i', 'ret = 42')])
"""
)
my_template.flush()
(p_mock, dbus_ultimate) = self.spawn_server_template(my_template.name, stdout=subprocess.PIPE)
self.addCleanup(p_mock.wait)
self.addCleanup(p_mock.terminate)
self.addCleanup(p_mock.stdout.close)
# ensure that we don't use/write any .pyc files, they are dangerous
# in a world-writable directory like /tmp
self.assertFalse(Path(my_template.name + "c").exists())
self.assertFalse(Path(importlib.util.cache_from_source(my_template.name)).exists())
loop = GLib.MainLoop()
caught_signals = []
def method_called(method, args, **_):
caught_signals.append((method, args))
loop.quit()
dbus_mock = dbus.Interface(dbus_ultimate, dbusmock.MOCK_IFACE)
dbus_mock.connect_to_signal("MethodCalled", method_called)
self.assertEqual(dbus_ultimate.Answer("foo"), 42)
self.assertEqual(dbus_ultimate.Answer("bar"), 42)
# should appear in introspection
xml = dbus_ultimate.Introspect()
self.assertIn('', xml)
self.assertIn('', xml)
# should not have ObjectManager API by default
self.assertRaises(dbus.exceptions.DBusException, dbus_ultimate.GetManagedObjects)
# Call should have been registered
mock_calls = dbus_mock.GetMethodCalls("Answer")
self.assertEqual(len(mock_calls), 2)
self.assertEqual(mock_calls[0][1], ["foo"])
self.assertEqual(mock_calls[1][1], ["bar"])
# Check signals
GLib.timeout_add(5000, loop.quit)
loop.run()
# only one signal because we call loop.quit() in the handler
self.assertEqual(len(caught_signals), 1)
method, args = caught_signals[0]
self.assertEqual(method, "Answer")
self.assertEqual(len(args), 1)
self.assertEqual(args[0], "foo")
def test_xdg_data_dir(self):
"""Load a template from XDG_DATA_DIRS"""
with tempfile.TemporaryDirectory(prefix="xdg-test") as xdg_data_dir:
os.environ["XDG_DATA_DIRS"] = "/non/existing:" + xdg_data_dir
template_dir = Path(xdg_data_dir) / "python-dbusmock" / "templates"
template_dir.mkdir(parents=True)
(template_dir / "answer.py").write_text(
"""
BUS_NAME = 'universe.Ultimate'
MAIN_OBJ = '/'
MAIN_IFACE = 'universe.Ultimate'
SYSTEM_BUS = False
def load(mock, parameters):
mock.AddMethods(MAIN_IFACE, [('Answer', 's', 'i', 'ret = 42')])
"""
)
(p_mock, dbus_ultimate) = self.spawn_server_template("answer", stdout=subprocess.PIPE)
self.addCleanup(p_mock.wait)
self.addCleanup(p_mock.terminate)
self.addCleanup(p_mock.stdout.close)
xml = dbus_ultimate.Introspect()
self.assertIn('', xml)
self.assertIn('', xml)
def test_static_method(self):
"""Static method in a template"""
with tempfile.NamedTemporaryFile(prefix="answer_", suffix=".py") as my_template:
my_template.write(
b"""import dbus
BUS_NAME = 'universe.Ultimate'
MAIN_OBJ = '/'
MAIN_IFACE = 'universe.Ultimate'
SYSTEM_BUS = False
def load(mock, parameters):
pass
@dbus.service.method(MAIN_IFACE,
in_signature='s',
out_signature='i')
def Answer(self, string):
return 42
"""
)
my_template.flush()
(p_mock, dbus_ultimate) = self.spawn_server_template(my_template.name, stdout=subprocess.PIPE)
self.addCleanup(p_mock.wait)
self.addCleanup(p_mock.terminate)
self.addCleanup(p_mock.stdout.close)
loop = GLib.MainLoop()
caught_signals = []
def method_called(method, args, **_):
caught_signals.append((method, args))
loop.quit()
dbus_mock = dbus.Interface(dbus_ultimate, dbusmock.MOCK_IFACE)
dbus_mock.connect_to_signal("MethodCalled", method_called)
self.assertEqual(dbus_ultimate.Answer("foo"), 42)
self.assertEqual(dbus_ultimate.Answer("bar"), 42)
# should appear in introspection
xml = dbus_ultimate.Introspect()
self.assertIn('', xml)
self.assertIn('', xml)
# Call should have been registered
mock_calls = dbus_mock.GetMethodCalls("Answer")
self.assertEqual(len(mock_calls), 2)
self.assertEqual(mock_calls[0][1], ["foo"])
self.assertEqual(mock_calls[1][1], ["bar"])
# Check signals
GLib.timeout_add(5000, loop.quit)
loop.run()
# only one signal because we call loop.quit() in the handler
self.assertEqual(len(caught_signals), 1)
method, args = caught_signals[0]
self.assertEqual(method, "Answer")
self.assertEqual(len(args), 1)
self.assertEqual(args[0], "foo")
def test_local_nonexisting(self):
self.assertRaises(ImportError, self.spawn_server_template, "/non/existing.py")
def test_explicit_bus_(self):
"""Explicitly set the bus for a template that does not specify SYSTEM_BUS"""
with tempfile.NamedTemporaryFile(prefix="answer_", suffix=".py") as my_template:
my_template.write(
b"""import dbus
BUS_NAME = 'universe.Ultimate'
MAIN_OBJ = '/'
MAIN_IFACE = 'universe.Ultimate'
def load(mock, parameters):
mock.AddMethods(MAIN_IFACE, [('Answer', '', 'i', 'ret = 42')])
"""
)
my_template.flush()
(p_mock, dbus_ultimate) = self.spawn_server_template(
my_template.name, stdout=subprocess.PIPE, system_bus=False
)
self.addCleanup(p_mock.wait)
self.addCleanup(p_mock.terminate)
self.addCleanup(p_mock.stdout.close)
self.wait_for_bus_object("universe.Ultimate", "/")
self.assertEqual(dbus_ultimate.Answer(), 42)
def test_override_bus_(self):
"""Override the bus for a template"""
with tempfile.NamedTemporaryFile(prefix="answer_", suffix=".py") as my_template:
my_template.write(
b"""import dbus
BUS_NAME = 'universe.Ultimate'
MAIN_OBJ = '/'
MAIN_IFACE = 'universe.Ultimate'
SYSTEM_BUS = True
def load(mock, parameters):
mock.AddMethods(MAIN_IFACE, [('Answer', '', 'i', 'ret = 42')])
"""
)
my_template.flush()
(p_mock, dbus_ultimate) = self.spawn_server_template(
my_template.name, stdout=subprocess.PIPE, system_bus=False
)
self.addCleanup(p_mock.wait)
self.addCleanup(p_mock.terminate)
self.addCleanup(p_mock.stdout.close)
self.wait_for_bus_object("universe.Ultimate", "/")
self.assertEqual(dbus_ultimate.Answer(), 42)
def test_object_manager(self):
"""Template with ObjectManager API"""
with tempfile.NamedTemporaryFile(prefix="objmgr_", suffix=".py") as my_template:
my_template.write(
b"""import dbus
BUS_NAME = 'org.test.Things'
MAIN_OBJ = '/org/test/Things'
IS_OBJECT_MANAGER = True
SYSTEM_BUS = False
def load(mock, parameters):
mock.AddObject('/org/test/Things/Thing1', 'org.test.Do', {'name': 'one'}, [])
mock.AddObject('/org/test/Things/Thing2', 'org.test.Do', {'name': 'two'}, [])
mock.AddObject('/org/test/Peer', 'org.test.Do', {'name': 'peer'}, [])
"""
)
my_template.flush()
(p_mock, dbus_objmgr) = self.spawn_server_template(my_template.name, stdout=subprocess.PIPE)
self.addCleanup(p_mock.wait)
self.addCleanup(p_mock.terminate)
self.addCleanup(p_mock.stdout.close)
# should have the two Things, but not the Peer
self.assertEqual(
dbus_objmgr.GetManagedObjects(),
{
"/org/test/Things/Thing1": {"org.test.Do": {"name": "one"}},
"/org/test/Things/Thing2": {"org.test.Do": {"name": "two"}},
},
)
# should appear in introspection
xml = dbus_objmgr.Introspect()
self.assertIn('', xml)
self.assertIn('', xml)
self.assertIn('', xml)
self.assertIn('', xml)
def test_add_object_with_subclass(self):
"""Template with DBusMockObject subclass"""
with tempfile.NamedTemporaryFile(prefix="objmgr_", suffix=".py") as my_template:
my_template.write(
b"""
import dbus
import dbusmock
BUS_NAME = 'org.test.Things'
MAIN_OBJ = '/org/test/Things'
MAIN_IFACE = 'org.test.Do'
SYSTEM_BUS = False
class Thing1(dbusmock.mockobject.DBusMockObject):
def __init__(self, *args, **kwargs):
super(Thing1, self).__init__(*args, **kwargs)
self.magic = kwargs.get('mock_data')
@dbus.service.method(MAIN_IFACE, in_signature='i', out_signature='i')
def Do0(self, input):
return self.magic + input
def load(mock, parameters):
mock.AddObject('/org/test/Things/Thing1', MAIN_IFACE, {}, [],
mock_class=Thing1,
mock_data=42)
"""
)
my_template.flush()
(p_mock, _) = self.spawn_server_template(my_template.name, stdout=subprocess.PIPE, system_bus=False)
self.addCleanup(p_mock.wait)
self.addCleanup(p_mock.terminate)
self.addCleanup(p_mock.stdout.close)
dbus_con = self.get_dbus(system_bus=False)
thing1 = dbus_con.get_object("org.test.Things", "/org/test/Things/Thing1")
self.assertEqual(thing1.Do0(0), 42)
self.assertEqual(thing1.Do0(1), 43)
def test_reset(self):
"""Reset() puts the template back to pristine state"""
(p_mock, obj_logind) = self.spawn_server_template("logind", stdout=subprocess.PIPE)
self.addCleanup(p_mock.wait)
self.addCleanup(p_mock.terminate)
self.addCleanup(p_mock.stdout.close)
# do some property, method, and object changes
obj_logind.Set("org.freedesktop.login1.Manager", "IdleAction", "frob")
mock_logind = dbus.Interface(obj_logind, dbusmock.MOCK_IFACE)
mock_logind.AddProperty("org.Test.Other", "walk", "silly")
mock_logind.AddMethod("", "DoWalk", "", "", "")
mock_logind.AddObject("/obj1", "", {}, [])
mock_logind.Reset()
# keeps the objects from the template
dbus_con = self.get_dbus(system_bus=True)
obj_logind = dbus_con.get_object("org.freedesktop.login1", "/org/freedesktop/login1")
self.assertEqual(obj_logind.CanSuspend(), "yes")
# resets properties
self.assertRaises(dbus.exceptions.DBusException, obj_logind.GetAll, "org.Test.Other")
self.assertEqual(obj_logind.Get("org.freedesktop.login1.Manager", "IdleAction"), "ignore")
# resets methods
self.assertRaises(dbus.exceptions.DBusException, obj_logind.DoWalk)
# resets other objects
obj1 = dbus_con.get_object("org.freedesktop.login1", "/obj1")
self.assertRaises(dbus.exceptions.DBusException, obj1.GetAll, "")
class TestCleanup(dbusmock.DBusTestCase):
"""Test cleanup of resources"""
def test_mock_terminates_with_bus(self):
"""Spawned mock processes exit when bus goes down"""
self.start_session_bus()
p_mock = self.spawn_server("org.freedesktop.Test", "/", "org.freedesktop.Test.Main")
assert self.session_bus_pid is not None
os.kill(self.session_bus_pid, signal.SIGTERM)
# give the mock 2 seconds to terminate
timeout = 20
while timeout > 0:
if p_mock.poll() is not None:
break
timeout -= 1
time.sleep(0.1)
if p_mock.poll() is None:
# clean up manually
p_mock.terminate()
p_mock.wait()
self.fail("mock process did not terminate after 2 seconds")
self.assertEqual(p_mock.wait(), 0)
class TestSubclass(dbusmock.DBusTestCase):
"""Test subclassing DBusMockObject"""
@classmethod
def setUpClass(cls):
cls.start_session_bus()
def test_ctor(self):
"""Override DBusMockObject constructor"""
class MyMock(dbusmock.mockobject.DBusMockObject):
def __init__(self):
bus_name = dbus.service.BusName("org.test.MyMock", dbusmock.testcase.DBusTestCase.get_dbus())
dbusmock.mockobject.DBusMockObject.__init__(self, bus_name, "/", "org.test.A", {}, os.devnull)
self.AddMethod("", "Ping", "", "i", "ret = 42")
m = MyMock()
self.assertEqual(m.Ping(), 42) # pylint: disable=no-member
def test_none_props(self):
"""object with None properties argument"""
class MyMock(dbusmock.mockobject.DBusMockObject):
def __init__(self):
bus_name = dbus.service.BusName("org.test.MyMock", dbusmock.testcase.DBusTestCase.get_dbus())
dbusmock.mockobject.DBusMockObject.__init__(
self, bus_name, "/mymock", "org.test.MyMockI", None, os.devnull
)
self.AddMethod("", "Ping", "", "i", "ret = 42")
m = MyMock()
self.assertEqual(m.Ping(), 42) # pylint: disable=no-member
self.assertEqual(m.GetAll("org.test.MyMockI"), {})
m.AddProperty("org.test.MyMockI", "blurb", 5)
self.assertEqual(m.GetAll("org.test.MyMockI"), {"blurb": 5})
class TestServiceAutostart(dbusmock.DBusTestCase):
"""Test service starting DBusMockObject"""
@classmethod
def setUpClass(cls):
cls.xdg_data_dir = tempfile.mkdtemp(prefix="dbusmock_xdg_")
os.environ["XDG_DATA_DIRS"] = cls.xdg_data_dir
dbus_dir = Path(cls.xdg_data_dir, "dbus-1")
system_dir = dbus_dir / "system-services"
session_dir = dbus_dir / "services"
system_dir.mkdir(parents=True)
session_dir.mkdir()
(system_dir / "org.TestSystem.service").write_text(
"[D-BUS Service]\n"
"Name=org.TestSystem\n"
'Exec=/usr/bin/python3 -c "import sys; from gi.repository import GLib, Gio; '
" Gio.bus_own_name(Gio.BusType.SYSTEM, 'org.TestSystem', 0, None, None, lambda *args: sys.exit(0)); "
' GLib.MainLoop().run()"\n'
"User=root"
)
(session_dir / "org.TestSession.service").write_text(
"[D-BUS Service]\n"
"Name=org.TestSession\n"
'Exec=/usr/bin/python3 -c "import sys; from gi.repository import GLib, Gio; '
" Gio.bus_own_name(Gio.BusType.SESSION, 'org.TestSession', 0, None, None, lambda *args: sys.exit(0)); "
' GLib.MainLoop().run()"\n'
"User=root"
)
cls.start_system_bus()
cls.start_session_bus()
@classmethod
def tearDownClass(cls):
shutil.rmtree(cls.xdg_data_dir)
dbusmock.DBusTestCase.tearDownClass()
def test_session_service_function_raise(self):
with self.assertRaises(AssertionError):
self.enable_service("does-not-exist")
with self.assertRaises(AssertionError):
self.disable_service("does-not-exist")
def test_session_service_isolation(self):
dbus_con = self.get_dbus(system_bus=False)
dbus_obj = dbus_con.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
dbus_if = dbus.Interface(dbus_obj, "org.freedesktop.DBus")
self.assertEqual(dbus_if.ListActivatableNames(), ["org.freedesktop.DBus"])
self.enable_service("org.TestSession")
self.addCleanup(self.disable_service, "org.TestSession")
self.assertEqual(dbus_if.ListActivatableNames(), ["org.freedesktop.DBus", "org.TestSession"])
def test_system_service_isolation(self):
dbus_con = self.get_dbus(system_bus=True)
dbus_obj = dbus_con.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
dbus_if = dbus.Interface(dbus_obj, "org.freedesktop.DBus")
self.assertEqual(dbus_if.ListActivatableNames(), ["org.freedesktop.DBus"])
self.enable_service("org.TestSystem", system_bus=True)
self.addCleanup(self.disable_service, "org.TestSystem", system_bus=True)
self.assertEqual(dbus_if.ListActivatableNames(), ["org.freedesktop.DBus", "org.TestSystem"])
def test_session_service_activation(self):
dbus_con = self.get_dbus(system_bus=False)
dbus_obj = dbus_con.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
dbus_if = dbus.Interface(dbus_obj, "org.freedesktop.DBus")
self.enable_service("org.TestSession")
self.addCleanup(self.disable_service, "org.TestSession")
dbus_if.StartServiceByName("org.TestSession", 0)
def test_system_service_activation(self):
dbus_con = self.get_dbus(system_bus=True)
dbus_obj = dbus_con.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
dbus_if = dbus.Interface(dbus_obj, "org.freedesktop.DBus")
self.enable_service("org.TestSystem", system_bus=True)
self.addCleanup(self.disable_service, "org.TestSystem", system_bus=True)
dbus_if.StartServiceByName("org.TestSystem", 0)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_api_pytest.py 0000644 0001751 0001751 00000002424 15071377476 021437 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2023 Martin Pitt
"""
import subprocess
import pytest
import dbusmock
def test_dbusmock_test(dbusmock_session):
assert dbusmock_session
test_iface = "org.freedesktop.Test.Main"
with dbusmock.SpawnedMock.spawn_for_name("org.freedesktop.Test", "/", test_iface) as server:
obj_test = server.obj
obj_test.AddMethod("", "Upper", "s", "s", "ret = args[0].upper()", interface_name=dbusmock.MOCK_IFACE)
assert obj_test.Upper("hello", interface=test_iface) == "HELLO"
@pytest.fixture(name="upower_mock")
def fixture_upower_mock(dbusmock_system):
assert dbusmock_system
with dbusmock.SpawnedMock.spawn_with_template("upower") as server:
yield server.obj
def test_dbusmock_test_template(upower_mock):
assert upower_mock
out = subprocess.check_output(["upower", "--dump"], text=True)
assert "version:" in out
assert "0.99" in out
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_bluez5.py 0000644 0001751 0001751 00000071145 15071377476 020472 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Philip Withnall"
__copyright__ = """
(c) 2013 Collabora Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import os
import re
import shutil
import subprocess
import sys
import time
import tracemalloc
import unittest
import xml.etree.ElementTree as ET
from pathlib import Path
import dbus
import dbus.mainloop.glib
from gi.repository import GLib
from packaging.version import Version
import dbusmock
tracemalloc.start(25)
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
have_bluetoothctl = shutil.which("bluetoothctl")
have_pbap_client = shutil.which("pbap-client")
os_release = Path("/etc/os-release")
el10 = os_release.exists() and "platform:el10" in os_release.read_text("UTF-8")
def _run_bluetoothctl(command):
"""Run bluetoothctl with the given command.
Return its output as a list of lines, with the command prompt removed
from each, and empty lines eliminated.
If bluetoothctl returns a non-zero exit code, raise an Exception.
"""
with subprocess.Popen(
["bluetoothctl"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True
) as process:
time.sleep(0.5) # give it time to query the bus
out, err = process.communicate(input="list\n" + command + "\nquit\n")
# Ignore output on stderr unless bluetoothctl dies.
if process.returncode != 0:
raise dbus.exceptions.DBusException(
f'bluetoothctl died with status {process.returncode} and errors: {err or ""}',
name="org.freedesktop.DBus.Mock.Error",
)
# Strip the prompt and escape sequences from the start of every line,
# then remove empty lines.
#
# The prompt looks like `[bluetooth]# `, potentially containing command
# line colour control codes.
def remove_prefix(line):
line = re.sub(r"\x1b\[[0-9;]*[mPK]", "", line)
line = re.sub(r"^\[bluetooth\]# ", "", line)
return line.strip()
lines = out.split("\n")
lines = map(remove_prefix, lines)
lines = filter(lambda line: line != "", lines)
# Filter out the echoed commands. (bluetoothctl uses readline.)
return list(filter(lambda line: line not in ["list", command, "quit"], lines))
def _introspect_property_types(obj, interface):
dbus_introspect = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
xml = dbus_introspect.Introspect()
root = ET.fromstring(xml)
prop_types = {}
for prop in root.findall(f'./interface[@name="{interface}"]/property'):
name, type_sig = prop.attrib["name"], prop.attrib["type"]
prop_types[name] = type_sig
return prop_types
@unittest.skipUnless(have_bluetoothctl, "bluetoothctl not installed")
class TestBlueZ5(dbusmock.DBusTestCase):
"""Test mocking bluetoothd"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
(cls.p_mock, cls.obj_bluez) = cls.spawn_server_template("bluez5", {}, stdout=subprocess.PIPE)
out = _run_bluetoothctl("version")
version = next(line.split(" ")[-1] for line in out if line.startswith("Version"))
cls.bluez5_version = Version(version)
def setUp(self):
self.obj_bluez.Reset()
self.dbusmock = dbus.Interface(self.obj_bluez, dbusmock.MOCK_IFACE)
self.dbusmock_bluez = dbus.Interface(self.obj_bluez, "org.bluez.Mock")
def test_no_adapters(self):
# Check for adapters.
out = _run_bluetoothctl("list")
for line in out:
self.assertFalse(line.startswith("Controller "))
def test_one_adapter(self):
# Chosen parameters.
adapter_name = "hci0"
system_name = "my-computer"
# Add an adapter
path = self.dbusmock_bluez.AddAdapter(adapter_name, system_name)
self.assertEqual(path, "/org/bluez/" + adapter_name)
adapter = self.dbus_con.get_object("org.bluez", path)
address = adapter.Get("org.bluez.Adapter1", "Address")
address_type = adapter.Get("org.bluez.Adapter1", "AddressType")
# Check for the adapter.
out = _run_bluetoothctl("list")
self.assertIn("Controller " + address + " " + system_name + " [default]", out)
out = _run_bluetoothctl("show " + address)
if address_type is not None:
self.assertIn(f"Controller {address} ({address_type})", out)
else:
self.assertIn("Controller " + address, out)
self.assertIn("Name: " + system_name, out)
self.assertIn("Alias: " + system_name, out)
self.assertIn("Powered: yes", out)
self.assertIn("Discoverable: no", out)
self.assertIn("Pairable: yes", out)
self.assertIn("Discovering: no", out)
self.assertIn("Roles: central", out)
self.assertIn("Roles: peripheral", out)
# Advertising Manager
self.assertIn("Advertising Features:", out)
self.assertIn("ActiveInstances: 0x00 (0)", out)
self.assertIn("SupportedInstances: 0x05 (5)", out)
self.assertIn("SupportedIncludes: tx-power", out)
self.assertIn("SupportedIncludes: appearance", out)
self.assertIn("SupportedIncludes: local-name", out)
self.assertIn("SupportedSecondaryChannels: 1M", out)
self.assertIn("SupportedSecondaryChannels: 2M", out)
# SupportedFeatures was added to the API with BlueZ 5.57
if self.bluez5_version >= Version("5.57"):
self.assertIn("SupportedFeatures: CanSetTxPower", out)
self.assertIn("SupportedFeatures: HardwareOffload", out)
# Capabilities key-value format was changed in BlueZ 5.70
if self.bluez5_version <= Version("5.70"):
capabilities = [
["SupportedCapabilities Key: MinTxPower", "SupportedCapabilities Value: -34"],
["SupportedCapabilities Key: MaxTxPower", "SupportedCapabilities Value: 7"],
["SupportedCapabilities Key: MaxAdvLen", "SupportedCapabilities Value: 0xfb (251)"],
["SupportedCapabilities Key: MaxScnRspLen", "SupportedCapabilities Value: 0xfb (251)"],
]
for capability in capabilities:
self.assertTrue(all(cap in out for cap in capability), f"Expected ${capability} in: ${out}")
else:
self.assertIn("SupportedCapabilities.MinTxPower: 0xffffffde (-34)", out)
self.assertIn("SupportedCapabilities.MaxTxPower: 0x0007 (7)", out)
self.assertIn("SupportedCapabilities.MaxAdvLen: 0xfb (251)", out)
self.assertIn("SupportedCapabilities.MaxScnRspLen: 0xfb (251)", out)
# Advertisement Monitor
self.assertIn("Advertisement Monitor Features:", out)
self.assertIn("SupportedMonitorTypes: or_patterns", out)
def test_adapter_property_types(self):
adapter_name = "hci0"
system_name = "my-computer"
path = self.dbusmock_bluez.AddAdapter(adapter_name, system_name)
self.assertEqual(path, "/org/bluez/" + adapter_name)
# Test that the property types on the interfaces are defined correctly
adapter = self.dbus_con.get_object("org.bluez", path)
adapter_prop_types = _introspect_property_types(adapter, "org.bluez.Adapter1")
self.assertEqual(
adapter_prop_types,
{
"Address": "s",
"AddressType": "s",
"Alias": "s",
"Class": "u",
"Discoverable": "b",
"DiscoverableTimeout": "u",
"Discovering": "b",
"Modalias": "s",
"Name": "s",
"Pairable": "b",
"PairableTimeout": "u",
"Powered": "b",
"Roles": "as",
"UUIDs": "as",
},
)
adv_manager_prop_types = _introspect_property_types(adapter, "org.bluez.LEAdvertisingManager1")
self.assertEqual(
adv_manager_prop_types,
{
"ActiveInstances": "y",
"SupportedCapabilities": "a{sv}",
"SupportedFeatures": "as",
"SupportedIncludes": "as",
"SupportedInstances": "y",
"SupportedSecondaryChannels": "as",
},
)
adv_monitor_manager_prop_types = _introspect_property_types(adapter, "org.bluez.AdvertisementMonitorManager1")
self.assertEqual(
adv_monitor_manager_prop_types,
{
"SupportedMonitorTypes": "as",
},
)
def test_no_devices(self):
# Add an adapter.
adapter_name = "hci0"
path = self.dbusmock_bluez.AddAdapter(adapter_name, "my-computer")
self.assertEqual(path, "/org/bluez/" + adapter_name)
# Check for devices.
out = _run_bluetoothctl("devices")
self.assertIn("Controller 00:01:02:03:04:05 my-computer [default]", out)
def test_one_device(self):
# Add an adapter.
adapter_name = "hci0"
path = self.dbusmock_bluez.AddAdapter(adapter_name, "my-computer")
self.assertEqual(path, "/org/bluez/" + adapter_name)
# Add a device.
address = "11:22:33:44:55:66"
alias = "My Phone"
path = self.dbusmock_bluez.AddDevice(adapter_name, address, alias)
self.assertEqual(path, "/org/bluez/" + adapter_name + "/dev_" + address.replace(":", "_"))
# Check for the device.
out = _run_bluetoothctl("devices")
self.assertIn("Device " + address + " " + alias, out)
# Check the device's properties.
out = "\n".join(_run_bluetoothctl("info " + address))
self.assertIn("Device " + address, out)
self.assertIn("Name: " + alias, out)
self.assertIn("Alias: " + alias, out)
self.assertIn("Paired: no", out)
self.assertIn("Trusted: no", out)
self.assertIn("Blocked: no", out)
self.assertIn("Connected: no", out)
def test_pairing_device(self):
# Add an adapter.
adapter_name = "hci0"
path = self.dbusmock_bluez.AddAdapter(adapter_name, "my-computer")
self.assertEqual(path, "/org/bluez/" + adapter_name)
# Add a device.
address = "11:22:33:44:55:66"
alias = "My Phone"
path = self.dbusmock_bluez.AddDevice(adapter_name, address, alias)
self.assertEqual(path, "/org/bluez/" + adapter_name + "/dev_" + address.replace(":", "_"))
# Pair with the device.
self.dbusmock_bluez.PairDevice(adapter_name, address)
# Check the device's properties.
out = "\n".join(_run_bluetoothctl("info " + address))
self.assertIn("Device " + address, out)
self.assertIn("Paired: yes", out)
def test_add_advertisement(self):
# When an advertisement is added
adv_path = self.dbusmock_bluez.AddAdvertisement("bc001")
# Then the path is returned
self.assertEqual(adv_path, "/org/dbusmock/bluez/advertisement/bc001")
# And the object is exported on the bus
adv = self.dbus_con.get_object("org.bluez", adv_path)
adv_type = adv.Get("org.bluez.LEAdvertisement1", "Type", dbus_interface=dbus.PROPERTIES_IFACE)
# And has the correct properties
self.assertEqual(adv_type, "broadcast")
def test_register_advertisement(self):
# Given an adapter with the LEAdvertisingManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_manager = dbus.Interface(adapter, "org.bluez.LEAdvertisingManager1")
props = dbus.Interface(adapter, dbus.PROPERTIES_IFACE)
active_instances = props.Get(adv_manager.dbus_interface, "ActiveInstances")
supported_instances = props.Get(adv_manager.dbus_interface, "SupportedInstances")
# And no active instances
self.assertEqual(active_instances, 0)
self.assertEqual(supported_instances, 5)
# When an advertisement is registered
# Then no error is raised
adv_manager.RegisterAdvertisement("/adv0", {})
# And active instances is incremented
active_instances = props.Get(adv_manager.dbus_interface, "ActiveInstances")
self.assertEqual(active_instances, 1)
# And supported instances is decremented
supported_instances = props.Get(adv_manager.dbus_interface, "SupportedInstances")
self.assertEqual(supported_instances, 4)
def test_register_advertisement_duplicate(self):
# Given an adapter with the LEAdvertisingManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_manager = dbus.Interface(adapter, "org.bluez.LEAdvertisingManager1")
props = dbus.Interface(adapter, dbus.PROPERTIES_IFACE)
# When an advertisement is registered twice
adv_manager.RegisterAdvertisement("/adv0", {})
# Then an error is raised
with self.assertRaisesRegex(dbus.exceptions.DBusException, "Already registered") as ctx:
adv_manager.RegisterAdvertisement("/adv0", {})
self.assertEqual(ctx.exception.get_dbus_name(), "org.bluez.Error.AlreadyExists")
# And active instances is not incremented
active_instances = props.Get(adv_manager.dbus_interface, "ActiveInstances")
self.assertEqual(active_instances, 1)
# And supported instances is not decremented
supported_instances = props.Get(adv_manager.dbus_interface, "SupportedInstances")
self.assertEqual(supported_instances, 4)
def test_register_advertisement_max_instances(self):
# Given an adapter with the LEAdvertisingManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_manager = dbus.Interface(adapter, "org.bluez.LEAdvertisingManager1")
props = dbus.Interface(adapter, dbus.PROPERTIES_IFACE)
max_instances = props.Get(adv_manager.dbus_interface, "SupportedInstances")
# When more advertisements are registered than supported
for i in range(max_instances):
adv_manager.RegisterAdvertisement(f"/adv{i}", {})
# Then an error is raised
with self.assertRaisesRegex(dbus.exceptions.DBusException, "Maximum number of advertisements reached") as ctx:
adv_manager.RegisterAdvertisement(f"/adv{int(max_instances)}", {})
self.assertEqual(ctx.exception.get_dbus_name(), "org.bluez.Error.NotPermitted")
# And active instances is equal to the number of supported instances
active_instances = props.Get(adv_manager.dbus_interface, "ActiveInstances")
self.assertEqual(active_instances, max_instances)
# And supported instances is now zero
supported_instances = props.Get(adv_manager.dbus_interface, "SupportedInstances")
self.assertEqual(supported_instances, 0)
def test_unregister_advertisement(self):
# Given an adapter with the LEAdvertisingManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_manager = dbus.Interface(adapter, "org.bluez.LEAdvertisingManager1")
props = dbus.Interface(adapter, dbus.PROPERTIES_IFACE)
# And a registered advertisement
adv_manager.RegisterAdvertisement("/adv0", {})
active_instances = props.Get(adv_manager.dbus_interface, "ActiveInstances")
supported_instances = props.Get(adv_manager.dbus_interface, "SupportedInstances")
self.assertEqual(active_instances, 1)
self.assertEqual(supported_instances, 4)
# When the advertisement is unregistered
# Then no error is raised
adv_manager.UnregisterAdvertisement("/adv0")
# And active instances is decremented
active_instances = props.Get(adv_manager.dbus_interface, "ActiveInstances")
self.assertEqual(active_instances, 0)
# And supported instances is incremented
supported_instances = props.Get(adv_manager.dbus_interface, "SupportedInstances")
self.assertEqual(supported_instances, 5)
def test_unregister_advertisement_unknown(self):
# Given an adapter with the LEAdvertisingManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_manager = dbus.Interface(adapter, "org.bluez.LEAdvertisingManager1")
# When an advertisement is unregistered without registering it first
# Then an error is raised
with self.assertRaisesRegex(dbus.exceptions.DBusException, "Unknown advertisement") as ctx:
adv_manager.UnregisterAdvertisement("/adv0")
self.assertEqual(ctx.exception.get_dbus_name(), "org.bluez.Error.DoesNotExist")
def test_add_monitor(self):
# When an advertisement monitor is added
adv_path = self.dbusmock_bluez.AddMonitor("mon001")
# Then the path is returned
self.assertEqual(adv_path, "/org/dbusmock/bluez/monitor/mon001")
# And the object is exported on the bus
adv = self.dbus_con.get_object("org.bluez", adv_path)
adv_type = adv.Get("org.bluez.AdvertisementMonitor1", "Type", dbus_interface=dbus.PROPERTIES_IFACE)
# And has the correct properties
self.assertEqual(adv_type, "or_patterns")
def test_register_monitor(self):
# Given an adapter with the AdvertisementMonitorManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_monitor_manager = dbus.Interface(adapter, "org.bluez.AdvertisementMonitorManager1")
# When an advertisement monitor is registered
# Then no error is raised
adv_monitor_manager.RegisterMonitor("/monitor0")
def test_register_monitor_duplicate(self):
# Given an adapter with the AdvertisementMonitorManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_monitor_manager = dbus.Interface(adapter, "org.bluez.AdvertisementMonitorManager1")
# When an advertisement monitor is registered twice
adv_monitor_manager.RegisterMonitor("/monitor0")
# Then an error is raised
with self.assertRaisesRegex(dbus.exceptions.DBusException, "Already registered") as ctx:
adv_monitor_manager.RegisterMonitor("/monitor0")
self.assertEqual(ctx.exception.get_dbus_name(), "org.bluez.Error.AlreadyExists")
def test_unregister_monitor(self):
# Given an adapter with the AdvertisementMonitorManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_monitor_manager = dbus.Interface(adapter, "org.bluez.AdvertisementMonitorManager1")
# And a registered advertisement monitor
adv_monitor_manager.RegisterMonitor("/monitor0")
# When the advertisement monitor is unregistered
# Then no error is raised
adv_monitor_manager.UnregisterMonitor("/monitor0")
def test_unregister_monitor_unknown(self):
# Given an adapter with the AdvertisementMonitorManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
adv_monitor_manager = dbus.Interface(adapter, "org.bluez.AdvertisementMonitorManager1")
# When an advertisement monitor is unregistered without registering it first
# Then an error is raised
with self.assertRaisesRegex(dbus.exceptions.DBusException, "Unknown monitor") as ctx:
adv_monitor_manager.UnregisterMonitor("/monitor0")
self.assertEqual(ctx.exception.get_dbus_name(), "org.bluez.Error.DoesNotExist")
@unittest.skipIf(el10, "https://issues.redhat.com/browse/RHEL-56021")
def test_advertise(self):
# Given an adapter with the LEAdvertisingManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
# When an advertisement is started via bluetoothctl
_run_bluetoothctl("advertise broadcast")
# Then the RegisterAdvertisement method was called
mock_calls = adapter.GetMethodCalls("RegisterAdvertisement", dbus_interface="org.freedesktop.DBus.Mock")
self.assertEqual(len(mock_calls), 1)
path, *_ = mock_calls[0][1]
self.assertEqual(path, "/org/bluez/advertising")
def test_monitor(self):
# Given an adapter with the AdvertisementMonitorManager1 interface
path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
adapter = self.dbus_con.get_object("org.bluez", path)
# When an advertisement monitor is configured via bluetoothctl
out = _run_bluetoothctl("monitor.add-or-pattern 0 255 01")
# Then bluetoothctl reports success
self.assertIn("Advertisement Monitor 0 added", out)
# And the RegisterMonitor method was called
mock_calls = adapter.GetMethodCalls("RegisterMonitor", dbus_interface="org.freedesktop.DBus.Mock")
self.assertEqual(len(mock_calls), 1)
path, *_ = mock_calls[0][1]
self.assertEqual(path, "/")
def test_register_agent(self):
# Given BlueZ with the AgentManager1 interface
bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
agent_manager = dbus.Interface(bluez, "org.bluez.AgentManager1")
agent_path = "/org/dbusmock/bluezagent"
# When an agent with the default capabiities is registered
# Then no error is raised
agent_manager.RegisterAgent(agent_path, "")
def test_register_agent_duplicate(self):
# Given BlueZ with the AgentManager1 interface
bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
agent_manager = dbus.Interface(bluez, "org.bluez.AgentManager1")
agent_path = "/org/dbusmock/bluezagent"
# When an agent is registered twice
agent_manager.RegisterAgent(agent_path, "")
# Then an error is raised
with self.assertRaisesRegex(
dbus.exceptions.DBusException, f"Another agent is already registered {agent_path}"
) as ctx:
agent_manager.RegisterAgent(agent_path, "")
self.assertEqual(ctx.exception.get_dbus_name(), "org.bluez.Error.AlreadyExists")
def test_unregister_agent(self):
# Given BlueZ with the AgentManager1 interface
bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
agent_manager = dbus.Interface(bluez, "org.bluez.AgentManager1")
agent_path = "/org/dbusmock/bluezagent"
# And a registered agent
agent_manager.RegisterAgent(agent_path, "")
# When the agent is unregistered
# Then no error is raised
agent_manager.UnregisterAgent(agent_path)
def test_unregister_agent_unknown(self):
# Given BlueZ with the AgentManager1 interface
bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
agent_manager = dbus.Interface(bluez, "org.bluez.AgentManager1")
agent_path = "/org/dbusmock/bluezagent"
# When an agent is unregistered without registering it first
# Then an error is raised
with self.assertRaisesRegex(dbus.exceptions.DBusException, f"Agent not registered {agent_path}") as ctx:
agent_manager.UnregisterAgent(agent_path)
self.assertEqual(ctx.exception.get_dbus_name(), "org.bluez.Error.DoesNotExist")
def test_agent(self):
# Given BlueZ with the AgentManager1 interface
bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
# When bluetoothctl is started
out = _run_bluetoothctl("list")
# Then it reports that the agent was registered
if self.bluez5_version >= Version("5.57"):
self.assertIn("Agent registered", out)
# And the RegisterAgent method was called
mock_calls = bluez.GetMethodCalls("RegisterAgent", dbus_interface="org.freedesktop.DBus.Mock")
self.assertEqual(len(mock_calls), 1)
path, capabilities = mock_calls[0][1]
self.assertEqual(path, "/org/bluez/agent")
self.assertEqual(capabilities, "")
@unittest.skipUnless(have_pbap_client, "pbap-client not installed (copy it from bluez/test)")
class TestBlueZObex(dbusmock.DBusTestCase):
"""Test mocking obexd"""
@classmethod
def setUpClass(cls):
cls.start_session_bus()
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
def setUp(self):
# bluetoothd
(self.p_mock, self.obj_bluez) = self.spawn_server_template("bluez5", {}, stdout=subprocess.PIPE)
self.dbusmock_bluez = dbus.Interface(self.obj_bluez, "org.bluez.Mock")
# obexd
(self.p_mock_obex, self.obj_obex) = self.spawn_server_template("bluez5-obex", {}, stdout=subprocess.PIPE)
self.dbusmock = dbus.Interface(self.obj_obex, dbusmock.MOCK_IFACE)
self.dbusmock_obex = dbus.Interface(self.obj_obex, "org.bluez.obex.Mock")
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
self.p_mock_obex.stdout.close()
self.p_mock_obex.terminate()
self.p_mock_obex.wait()
def test_everything(self):
# Set up an adapter and device.
adapter_name = "hci0"
device_address = "11:22:33:44:55:66"
device_alias = "My Phone"
ml = GLib.MainLoop()
self.dbusmock_bluez.AddAdapter(adapter_name, "my-computer")
self.dbusmock_bluez.AddDevice(adapter_name, device_address, device_alias)
self.dbusmock_bluez.PairDevice(adapter_name, device_address)
transferred_files = []
def _transfer_created_cb(path, params, transfer_filename):
bus = self.get_dbus(False)
obj = bus.get_object("org.bluez.obex", path)
transfer = dbus.Interface(obj, "org.bluez.obex.transfer1.Mock")
Path(transfer_filename).write_bytes(
b"BEGIN:VCARD\r\n"
b"VERSION:3.0\r\n"
b"FN:Forrest Gump\r\n"
b"TEL;TYPE=WORK,VOICE:(111) 555-1212\r\n"
b"TEL;TYPE=HOME,VOICE:(404) 555-1212\r\n"
b"EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com\r\n"
b"EMAIL:test@example.com\r\n"
b"URL;TYPE=HOME:http://example.com/\r\n"
b"URL:http://forest.com/\r\n"
b"URL:https://test.com/\r\n"
b"END:VCARD\r\n"
)
transfer.UpdateStatus(True)
transferred_files.append(transfer_filename)
self.dbusmock_obex.connect_to_signal("TransferCreated", _transfer_created_cb)
# Run pbap-client, then run the GLib main loop. The main loop will quit
# after a timeout, at which point the code handles output from
# pbap-client and waits for it to terminate. Integrating
# process.communicate() with the GLib main loop to avoid the timeout is
# too difficult.
with subprocess.Popen(
["pbap-client", device_address], stdout=subprocess.PIPE, stderr=sys.stderr, universal_newlines=True
) as process:
GLib.timeout_add(5000, ml.quit)
ml.run()
out = process.communicate()[0]
lines = out.split("\n")
lines = filter(lambda line: line != "", lines)
lines = list(lines)
# Clean up the transferred files.
for f in transferred_files:
try:
os.remove(f)
except OSError:
pass
# See what pbap-client sees.
self.assertIn("Creating Session", lines)
self.assertIn("--- Select Phonebook PB ---", lines)
self.assertIn("--- GetSize ---", lines)
self.assertIn("Size = 0", lines)
self.assertIn("--- List vCard ---", lines)
self.assertIn("Transfer /org/bluez/obex/client/session0/transfer0 complete", lines)
self.assertIn("Transfer /org/bluez/obex/client/session0/transfer1 complete", lines)
self.assertIn("Transfer /org/bluez/obex/client/session0/transfer2 complete", lines)
self.assertIn("Transfer /org/bluez/obex/client/session0/transfer3 complete", lines)
self.assertIn("FINISHED", lines)
self.assertNotIn("ERROR", lines)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_cli.py 0000644 0001751 0001751 00000022351 15071377476 020026 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import importlib.util
import shutil
import subprocess
import sys
import tempfile
import tracemalloc
import unittest
from pathlib import Path
import dbus
import dbusmock
tracemalloc.start(25)
have_upower = shutil.which("upower")
have_gdbus = shutil.which("gdbus")
class TestCLI(dbusmock.DBusTestCase):
"""Test running dbusmock from the command line"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.start_session_bus()
cls.system_con = cls.get_dbus(True)
cls.session_con = cls.get_dbus()
def setUp(self):
self.p_mock = None
def tearDown(self):
if self.p_mock:
if self.p_mock.stdout:
self.p_mock.stdout.close()
if self.p_mock.stderr:
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
self.p_mock = None
def start_mock(self, args, wait_name, wait_path, wait_system=False):
# pylint: disable=consider-using-with
self.p_mock = subprocess.Popen(
[sys.executable, "-m", "dbusmock", *args], stdout=subprocess.PIPE, universal_newlines=True
)
self.wait_for_bus_object(wait_name, wait_path, wait_system)
def start_mock_process(self, args):
return subprocess.check_output([sys.executable, "-m", "dbusmock", *args], text=True)
def test_session_bus(self):
self.start_mock(["com.example.Test", "/", "TestIface"], "com.example.Test", "/")
def test_system_bus(self):
self.start_mock(["--system", "com.example.Test", "/", "TestIface"], "com.example.Test", "/", True)
def test_template_upower(self):
self.start_mock(["-t", "upower"], "org.freedesktop.UPower", "/org/freedesktop/UPower", True)
self.check_upower_running()
def test_template_upower_explicit_path(self):
spec = importlib.util.find_spec("dbusmock.templates.upower")
self.assertTrue(Path(spec.origin).exists())
self.start_mock(["-t", spec.origin], "org.freedesktop.UPower", "/org/freedesktop/UPower", True)
self.check_upower_running()
def check_upower_running(self):
# check that it actually ran the template, if we have upower
if have_upower:
out = subprocess.check_output(["upower", "--dump"], text=True)
self.assertRegex(out, r"on-battery:\s+no")
mock_out = self.p_mock.stdout.readline()
self.assertTrue("EnumerateDevices" in mock_out or "GetAll" in mock_out, mock_out)
def test_template_explicit_system(self):
# --system is redundant here, but should not break
self.start_mock(["--system", "-t", "upower"], "org.freedesktop.UPower", "/org/freedesktop/UPower", True)
self.check_upower_running()
def test_template_override_session(self):
self.start_mock(["--session", "-t", "upower"], "org.freedesktop.UPower", "/org/freedesktop/UPower", False)
def test_template_conflicting_bus(self):
with self.assertRaises(subprocess.CalledProcessError) as cm:
subprocess.check_output(
[sys.executable, "-m", "dbusmock", "--system", "--session", "-t", "upower"],
stderr=subprocess.STDOUT,
text=True,
)
err = cm.exception
self.assertEqual(err.returncode, 2)
self.assertRegex(err.output, "--system.*--session.*exclusive")
def test_template_parameters(self):
self.start_mock(
["-t", "upower", "-p", '{"DaemonVersion": "0.99.0", "OnBattery": true}'],
"org.freedesktop.UPower",
"/org/freedesktop/UPower",
True,
)
# check that it actually ran the template, if we have upower
if have_upower:
out = subprocess.check_output(["upower", "--dump"], text=True)
self.assertRegex(out, r"daemon-version:\s+0\.99\.0")
self.assertRegex(out, r"on-battery:\s+yes")
def test_template_parameters_malformed_json(self):
with self.assertRaises(subprocess.CalledProcessError) as cm:
subprocess.check_output(
[sys.executable, "-m", "dbusmock", "-t", "upower", "-p", '{"DaemonVersion: "0.99.0"}'],
stderr=subprocess.STDOUT,
text=True,
)
err = cm.exception
self.assertEqual(err.returncode, 2)
self.assertRegex(err.output, "Malformed JSON given for parameters:.* delimiter")
def test_template_parameters_not_dict(self):
with self.assertRaises(subprocess.CalledProcessError) as cm:
subprocess.check_output(
[sys.executable, "-m", "dbusmock", "-t", "upower", "-p", '"banana"'],
stderr=subprocess.STDOUT,
text=True,
)
err = cm.exception
self.assertEqual(err.returncode, 2)
self.assertEqual(err.output, "JSON parameters must be a dictionary\n")
@unittest.skipIf(not have_upower, "No upower installed")
def test_template_upower_exec(self):
out = self.start_mock_process(["-t", "upower", "--exec", "upower", "--dump"])
self.assertRegex(out, r"on-battery:\s+no")
self.assertRegex(out, r"daemon-version:\s+0\.99")
@unittest.skipIf(not have_gdbus, "No gdbus installed")
def test_manual_upower_exec(self):
out = self.start_mock_process(
[
"--system",
"org.freedesktop.UPower",
"/org/freedesktop/UPower",
"org.freedesktop.UPower",
"--exec",
"gdbus",
"introspect",
"--system",
"--dest",
"org.freedesktop.UPower",
"--object-path",
"/org/freedesktop/UPower",
]
)
self.assertRegex(out, r"AddMethod\(")
self.assertRegex(out, r"AddMethods\(")
def test_template_local(self):
with tempfile.NamedTemporaryFile(prefix="answer_", suffix=".py") as my_template:
my_template.write(
b"""import dbus
BUS_NAME = 'universe.Ultimate'
MAIN_OBJ = '/'
MAIN_IFACE = 'universe.Ultimate'
SYSTEM_BUS = False
def load(mock, parameters):
mock.AddMethods(MAIN_IFACE, [('Answer', '', 'i', 'ret = 42')])
"""
)
my_template.flush()
# template specifies session bus
self.start_mock(["-t", my_template.name], "universe.Ultimate", "/", False)
obj = self.session_con.get_object("universe.Ultimate", "/")
if_u = dbus.Interface(obj, "universe.Ultimate")
self.assertEqual(if_u.Answer(), 42)
def test_template_override_system(self):
with tempfile.NamedTemporaryFile(prefix="answer_", suffix=".py") as my_template:
my_template.write(
b"""import dbus
BUS_NAME = 'universe.Ultimate'
MAIN_OBJ = '/'
MAIN_IFACE = 'universe.Ultimate'
SYSTEM_BUS = False
def load(mock, parameters):
mock.AddMethods(MAIN_IFACE, [('Answer', '', 'i', 'ret = 42')])
"""
)
my_template.flush()
# template specifies session bus, but CLI overrides to system
self.start_mock(["--system", "-t", my_template.name], "universe.Ultimate", "/", True)
obj = self.system_con.get_object("universe.Ultimate", "/")
if_u = dbus.Interface(obj, "universe.Ultimate")
self.assertEqual(if_u.Answer(), 42)
def test_object_manager(self):
self.start_mock(["-m", "com.example.Test", "/", "TestIface"], "com.example.Test", "/")
obj = self.session_con.get_object("com.example.Test", "/")
if_om = dbus.Interface(obj, dbusmock.OBJECT_MANAGER_IFACE)
self.assertEqual(if_om.GetManagedObjects(), {})
# add a new object, should appear
obj.AddObject("/a/b", "org.Test", {"name": "foo"}, dbus.Array([], signature="(ssss)"))
self.assertEqual(if_om.GetManagedObjects(), {"/a/b": {"org.Test": {"name": "foo"}}})
def test_no_args(self):
with subprocess.Popen(
[sys.executable, "-m", "dbusmock"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
) as p:
(out, err) = p.communicate()
self.assertEqual(out, "")
self.assertIn("must specify NAME", err)
self.assertNotEqual(p.returncode, 0)
def test_help(self):
with subprocess.Popen(
[sys.executable, "-m", "dbusmock", "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
) as p:
(out, err) = p.communicate()
self.assertEqual(err, "")
self.assertIn("INTERFACE", out)
self.assertIn("--system", out)
self.assertEqual(p.returncode, 0)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_code.py 0000644 0001751 0001751 00000005236 15071377476 020174 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import glob
import importlib.util
import os
import subprocess
import sys
import unittest
@unittest.skipIf(
os.getenv("SKIP_STATIC_CHECKS", "0") == "1", "$SKIP_STATIC_CHECKS set, not running static code checks"
)
class StaticCodeTests(unittest.TestCase):
@unittest.skipUnless(importlib.util.find_spec("pylint"), "pylint not available, skipping")
def test_pylint(self):
subprocess.check_call([sys.executable, "-m", "pylint", *glob.glob("dbusmock/*.py")])
# signatures/arguments are not determined by us, docstrings are a bit pointless, and code repetition
# is impractical to avoid (e.g. bluez4 and bluez5)
subprocess.check_call(
[
sys.executable,
"-m",
"pylint",
"--score=n",
"--disable=missing-function-docstring,R0801",
"--disable=too-many-arguments,too-many-instance-attributes",
"--disable=too-few-public-methods",
"dbusmock/templates/",
]
)
subprocess.check_call(
[
sys.executable,
"-m",
"pylint",
"--score=n",
"--disable=missing-module-docstring,missing-class-docstring",
"--disable=missing-function-docstring",
"--disable=too-many-public-methods,too-many-lines,too-many-statements,R0801",
"--disable=fixme",
"tests/",
]
)
@unittest.skipUnless(importlib.util.find_spec("mypy"), "mypy not available, skipping")
def test_types(self):
subprocess.check_call([sys.executable, "-m", "mypy", "dbusmock/", "tests/"])
def test_ruff(self):
try:
subprocess.check_call(["ruff", "check", "--no-cache", "."])
except FileNotFoundError:
self.skipTest("ruff not available, skipping")
def test_black(self):
try:
subprocess.check_call(["black", "--check", "--diff", "."])
except FileNotFoundError:
self.skipTest("black not available, skipping")
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_gnome_screensaver.py 0000644 0001751 0001751 00000004431 15071377476 022763 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import fcntl
import os
import subprocess
import sys
import unittest
import dbusmock
class TestGnomeScreensaver(dbusmock.DBusTestCase):
"""Test mocking gnome-screensaver"""
@classmethod
def setUpClass(cls):
cls.start_session_bus()
cls.dbus_con = cls.get_dbus(False)
def setUp(self):
(self.p_mock, self.obj_ss) = self.spawn_server_template("gnome_screensaver", {}, stdout=subprocess.PIPE)
# set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_default_state(self):
"""Not locked by default"""
self.assertEqual(self.obj_ss.GetActive(), False)
def test_lock(self):
"""Lock()"""
self.obj_ss.Lock()
self.assertEqual(self.obj_ss.GetActive(), True)
self.assertGreater(self.obj_ss.GetActiveTime(), 0)
self.assertRegex(
self.p_mock.stdout.read(), b"emit /org/gnome/ScreenSaver org.gnome.ScreenSaver.ActiveChanged True\n"
)
def test_set_active(self):
"""SetActive()"""
self.obj_ss.SetActive(True)
self.assertEqual(self.obj_ss.GetActive(), True)
self.assertRegex(
self.p_mock.stdout.read(), b"emit /org/gnome/ScreenSaver org.gnome.ScreenSaver.ActiveChanged True\n"
)
self.obj_ss.SetActive(False)
self.assertEqual(self.obj_ss.GetActive(), False)
self.assertRegex(
self.p_mock.stdout.read(), b"emit /org/gnome/ScreenSaver org.gnome.ScreenSaver.ActiveChanged False\n"
)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_gsd_rfkill.py 0000644 0001751 0001751 00000004113 15071377476 021373 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Guido Günther"
__copyright__ = "2024 The Phosh Developers"
import fcntl
import os
import subprocess
import sys
import unittest
import dbus
import dbusmock
class TestGsdRfkill(dbusmock.DBusTestCase):
"""Test mocked GNOME Settings Daemon Rfkill"""
@classmethod
def setUpClass(cls):
cls.start_session_bus()
cls.dbus_con = cls.get_dbus()
def setUp(self):
(self.p_mock, self.p_obj) = self.spawn_server_template("gsd_rfkill", {}, stdout=subprocess.PIPE)
# set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_mainobject(self):
propiface = dbus.Interface(self.p_obj, dbus.PROPERTIES_IFACE)
mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "AirplaneMode")
self.assertEqual(mode, False)
mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "HasAirplaneMode")
self.assertEqual(mode, True)
def test_airplane_mode(self):
propiface = dbus.Interface(self.p_obj, dbus.PROPERTIES_IFACE)
self.p_obj.SetAirplaneMode(True)
mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "AirplaneMode")
self.assertEqual(mode, True)
mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "BluetoothAirplaneMode")
self.assertEqual(mode, True)
mode = propiface.Get("org.gnome.SettingsDaemon.Rfkill", "WwanAirplaneMode")
self.assertEqual(mode, True)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_logind.py 0000644 0001751 0001751 00000012266 15071377476 020537 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import re
import shutil
import subprocess
import sys
import tracemalloc
import unittest
from pathlib import Path
import dbus
import dbusmock
tracemalloc.start(25)
have_loginctl = shutil.which("loginctl")
@unittest.skipUnless(have_loginctl, "loginctl not installed")
@unittest.skipUnless(Path("/run/systemd/system").exists(), "/run/systemd/system does not exist")
class TestLogind(dbusmock.DBusTestCase):
"""Test mocking logind"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
if have_loginctl:
out = subprocess.check_output(["loginctl", "--version"], text=True)
cls.version = re.search(r"(\d+)", out.splitlines()[0]).group(1)
def setUp(self):
self.p_mock = None
def tearDown(self):
if self.p_mock:
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_empty(self):
(self.p_mock, _) = self.spawn_server_template("logind", {}, stdout=subprocess.PIPE)
cmd = ["loginctl"]
if self.version >= "209":
cmd.append("--no-legend")
out = subprocess.check_output([*cmd, "list-sessions"], text=True)
self.assertEqual(out, "")
out = subprocess.check_output([*cmd, "list-seats"], text=True)
self.assertEqual(out, "")
out = subprocess.check_output([*cmd, "list-users"], text=True)
self.assertEqual(out, "")
def test_session(self):
(self.p_mock, obj_logind) = self.spawn_server_template("logind", {}, stdout=subprocess.PIPE)
obj_logind.AddSession("c1", "seat0", 500, "joe", True)
out = subprocess.check_output(["loginctl", "list-seats"], text=True)
self.assertRegex(out, r"(^|\n)seat0\s+")
out = subprocess.check_output(["loginctl", "show-seat", "seat0"], text=True)
self.assertRegex(out, "Id=seat0")
if self.version <= "208":
self.assertRegex(out, "ActiveSession=c1")
self.assertRegex(out, "Sessions=c1")
out = subprocess.check_output(["loginctl", "list-users"], text=True)
self.assertRegex(out, r"(^|\n)\s*500\s+joe\s*")
# note, this does an actual getpwnam() in the client, so we cannot call
# this with hardcoded user names; get from actual user in the system
# out = subprocess.check_output(['loginctl', 'show-user', 'joe'],
# universal_newlines=True)
# self.assertRegex(out, 'UID=500')
# self.assertRegex(out, 'GID=500')
# self.assertRegex(out, 'Name=joe')
# self.assertRegex(out, 'Sessions=c1')
# self.assertRegex(out, 'State=active')
out = subprocess.check_output(["loginctl", "list-sessions"], text=True)
self.assertRegex(out, "c1 +500 +joe +seat0")
out = subprocess.check_output(["loginctl", "show-session", "c1"], text=True)
self.assertRegex(out, "Id=c1")
self.assertRegex(out, "Class=user")
self.assertRegex(out, "Active=yes")
self.assertRegex(out, "State=active")
self.assertRegex(out, "Name=joe")
self.assertRegex(out, "LockedHint=no")
session_mock = dbus.Interface(
self.dbus_con.get_object("org.freedesktop.login1", "/org/freedesktop/login1/session/c1"),
"org.freedesktop.login1.Session",
)
session_mock.SetLockedHint(True)
out = subprocess.check_output(["loginctl", "show-session", "c1"], text=True)
self.assertRegex(out, "Id=c1")
self.assertRegex(out, "LockedHint=yes")
def test_properties(self):
(self.p_mock, obj_logind) = self.spawn_server_template("logind", {}, stdout=subprocess.PIPE)
props = obj_logind.GetAll("org.freedesktop.login1.Manager", interface=dbus.PROPERTIES_IFACE)
self.assertEqual(props["PreparingForSleep"], False)
self.assertEqual(props["IdleSinceHint"], 0)
def test_inhibit(self):
(self.p_mock, obj_logind) = self.spawn_server_template("logind", {}, stdout=subprocess.PIPE)
# what, who, why, mode
fd = obj_logind.Inhibit("suspend", "testcode", "purpose", "delay")
# Our inhibitor is held
out = subprocess.check_output(["systemd-inhibit"], text=True)
self.assertRegex(
out.replace("\n", " "),
"(testcode +[0-9]+ +[^ ]* +[0-9]+ +[^ ]* +suspend purpose delay)|"
"(Who: testcode.*What: suspend.*Why: purpose.*Mode: delay.*)",
)
del fd
# No inhibitor is held
out = subprocess.check_output(["systemd-inhibit"], text=True)
self.assertRegex(out, "No inhibitors|0 inhibitors listed")
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_low_memory_monitor.py 0000644 0001751 0001751 00000003561 15071377476 023221 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Bastien Nocera"
__copyright__ = """
(c) 2019 Red Hat Inc.
(c) 2017 - 2022 Martin Pitt
"""
import fcntl
import os
import subprocess
import sys
import unittest
import dbus
import dbus.mainloop.glib
import dbusmock
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
class TestLowMemoryMonitor(dbusmock.DBusTestCase):
"""Test mocking low-memory-monitor"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
def setUp(self):
(self.p_mock, self.obj_lmm) = self.spawn_server_template("low_memory_monitor", {}, stdout=subprocess.PIPE)
# set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.last_warning = -1
self.dbusmock = dbus.Interface(self.obj_lmm, dbusmock.MOCK_IFACE)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_low_memory_warning_signal(self):
"""LowMemoryWarning signal"""
self.dbusmock.EmitWarning(100)
log = self.p_mock.stdout.read()
self.assertRegex(log, b"[0-9.]+ emit .*LowMemoryWarning 100\n")
self.dbusmock.EmitWarning(255)
log = self.p_mock.stdout.read()
self.assertRegex(log, b"[0-9.]+ emit .*LowMemoryWarning 255\n")
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_modemmanager.py 0000644 0001751 0001751 00000013031 15071377476 021706 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Guido Günther"
__copyright__ = """
(c) 2024 The Phosh Developers
"""
import shutil
import subprocess
import sys
import unittest
import dbus
import dbus.mainloop.glib
import dbusmock
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
mmcli_has_cbm_support = False
have_mmcli = shutil.which("mmcli")
if have_mmcli:
out = subprocess.run(["mmcli", "--help"], capture_output=True, text=True) # pylint: disable=subprocess-run-check
mmcli_has_cbm_support = "--help-cell-broadcast" in out.stdout
class TestModemManagerBase(dbusmock.DBusTestCase):
"""Test mocking ModemManager"""
dbus_interface = ""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
def setUp(self):
super().setUp()
(self.p_mock, self.p_obj) = self.spawn_server_template("modemmanager", {}, stdout=subprocess.PIPE)
def tearDown(self):
if self.p_mock:
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
super().tearDown()
def get_property(self, name):
return self.p_obj.Get(self.dbus_interface, name, dbus_interface=dbus.PROPERTIES_IFACE)
@unittest.skipUnless(have_mmcli, "mmcli utility not available")
class TestModemManagerMmcliBase(TestModemManagerBase):
"""Base ModemManager interface tests using mmcli"""
ret = None
def run_mmcli(self, args):
self.assertIsNone(self.ret)
self.ret = subprocess.run( # pylint: disable=subprocess-run-check
["mmcli", *args], capture_output=True, text=True
)
def assertOutputEquals(self, expected_lines):
self.assertIsNotNone(self.ret)
lines = self.ret.stdout.split("\n")
self.assertEqual(len(lines), len(expected_lines))
for expected, line in zip(expected_lines, lines):
self.assertEqual(expected, line)
def assertOutputContainsLine(self, expected_line, ret=0):
self.assertEqual(self.ret.returncode, ret)
self.assertIn(expected_line, self.ret.stdout)
class TestModemManagerModemMmcli(TestModemManagerMmcliBase):
"""main ModemManager interface tests using mmcli"""
def test_no_modems(self):
self.run_mmcli(["-m", "any"])
self.assertEqual(self.ret.returncode, 1)
self.assertIn("error: couldn't find modem", self.ret.stderr)
def test_modem(self):
self.p_obj.AddSimpleModem()
self.run_mmcli(["-m", "any"])
self.assertOutputEquals(
[
" -----------------------------",
" General | path: /org/freedesktop/ModemManager1/Modems/8",
" -----------------------------",
" Hardware | model: E1750",
" | firmware revision: 11.126.08.01.00",
" -----------------------------",
" Status | state: enabled",
" | power state: on",
" | access tech: lte",
" | signal quality: 70% (recent)",
" -----------------------------",
" Modes | supported: allowed: 4g; preferred: 4g",
" | allowed: 2g, 3g; preferred: 3g",
" | current: allowed: 4g; preferred: 4g",
" -----------------------------",
" 3GPP | imei: doesnotmatter",
" | operator id: 00101",
" | operator name: TheOperator",
" | registration: idle",
" -----------------------------",
" SIM | primary sim path: /org/freedesktop/ModemManager1/SIM/2",
"",
]
)
def test_sim(self):
self.p_obj.AddSimpleModem()
self.run_mmcli(["-i", "any"])
self.assertOutputEquals(
[
" --------------------",
" General | path: /org/freedesktop/ModemManager1/SIM/2",
" --------------------",
" Properties | active: yes",
" | imsi: doesnotmatter",
"",
]
)
def test_voice_call_list(self):
self.p_obj.AddSimpleModem()
self.run_mmcli(["-m", "any", "--voice-list-calls"])
self.assertOutputContainsLine("No calls were found\n")
def test_voice_status(self):
self.p_obj.AddSimpleModem()
self.run_mmcli(["-m", "any", "--voice-status"])
self.assertOutputContainsLine("emergency only: no\n")
@unittest.skipUnless(mmcli_has_cbm_support, "mmcli has no CBM suppot")
def test_cbm(self):
self.p_obj.AddSimpleModem()
self.p_obj.AddCbm(2, 4383, "This is a test")
self.run_mmcli(["-m", "any", "--cell-broadcast-list-cbm"])
self.assertOutputEquals(
[
" /org/freedesktop/ModemManager1/Cbm/1 (received)",
"",
]
)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_networkmanager.py 0000644 0001751 0001751 00000057440 15071377476 022312 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Iftikhar Ahmad"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import os
import re
import shutil
import subprocess
import sys
import tracemalloc
import unittest
import dbus
import dbus.mainloop.glib
from gi.repository import GLib
from packaging.version import Version
import dbusmock
from dbusmock.templates.networkmanager import (
CSETTINGS_IFACE,
MANAGER_IFACE,
SETTINGS_IFACE,
SETTINGS_OBJ,
DeviceState,
InfrastructureMode,
NM80211ApSecurityFlags,
NMActiveConnectionState,
NMConnectivityState,
NMState,
)
tracemalloc.start(25)
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
have_nmcli = shutil.which("nmcli")
@unittest.skipUnless(have_nmcli, "nmcli not installed")
class TestNetworkManager(dbusmock.DBusTestCase):
"""Test mocking NetworkManager"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
os.environ["G_DEBUG"] = "fatal-warnings,fatal-criticals"
# prepare environment which avoids translations
cls.lang_env = os.environ.copy()
try:
del cls.lang_env["LANG"]
except KeyError:
pass
try:
del cls.lang_env["LANGUAGE"]
except KeyError:
pass
cls.lang_env["LC_MESSAGES"] = "C"
def setUp(self):
(self.p_mock, self.obj_networkmanager) = self.spawn_server_template(
"networkmanager", {"NetworkingEnabled": True, "WwanEnabled": False}, stdout=subprocess.PIPE
)
self.dbusmock = dbus.Interface(self.obj_networkmanager, dbusmock.MOCK_IFACE)
self.settings = dbus.Interface(self.dbus_con.get_object(MANAGER_IFACE, SETTINGS_OBJ), SETTINGS_IFACE)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def read_general(self):
return subprocess.check_output(["nmcli", "--nocheck", "general"], env=self.lang_env, text=True)
def read_networking(self):
return subprocess.check_output(["nmcli", "--nocheck", "networking"], env=self.lang_env, text=True)
def read_connection(self):
return subprocess.check_output(["nmcli", "--nocheck", "connection"], env=self.lang_env, text=True)
def read_active_connection(self):
return subprocess.check_output(
["nmcli", "--nocheck", "connection", "show", "--active"], env=self.lang_env, text=True
)
def read_device(self):
return subprocess.check_output(["nmcli", "--nocheck", "dev"], env=self.lang_env, text=True)
def read_device_wifi(self):
return subprocess.check_output(
["nmcli", "--nocheck", "dev", "wifi", "list", "--rescan", "no"],
env=self.lang_env,
text=True,
)
def test_one_eth_disconnected(self):
self.dbusmock.AddEthernetDevice("mock_Ethernet1", "eth0", DeviceState.DISCONNECTED)
out = self.read_device()
self.assertRegex(out, r"eth0.*\sdisconnected")
def test_one_eth_connected(self):
self.dbusmock.AddEthernetDevice("mock_Ethernet1", "eth0", DeviceState.ACTIVATED)
out = self.read_device()
self.assertRegex(out, r"eth0.*\sconnected")
def test_two_eth(self):
# test with numeric state value
self.dbusmock.AddEthernetDevice("mock_Ethernet1", "eth0", 30)
self.dbusmock.AddEthernetDevice("mock_Ethernet2", "eth1", DeviceState.ACTIVATED)
out = self.read_device()
self.assertRegex(out, r"eth0.*\sdisconnected")
self.assertRegex(out, r"eth1.*\sconnected")
def test_wifi_without_access_points(self):
self.dbusmock.AddWiFiDevice("mock_WiFi1", "wlan0", DeviceState.ACTIVATED)
out = self.read_device()
self.assertRegex(out, r"wlan0.*\sconnected")
def test_eth_and_wifi(self):
self.dbusmock.AddEthernetDevice("mock_Ethernet1", "eth0", DeviceState.DISCONNECTED)
self.dbusmock.AddWiFiDevice("mock_WiFi1", "wlan0", DeviceState.ACTIVATED)
out = self.read_device()
self.assertRegex(out, r"eth0.*\sdisconnected")
self.assertRegex(out, r"wlan0.*\sconnected")
def test_one_wifi_with_accesspoints(self):
wifi = self.dbusmock.AddWiFiDevice("mock_WiFi2", "wlan0", DeviceState.ACTIVATED)
self.dbusmock.AddAccessPoint(
wifi,
"Mock_AP1",
"AP_1",
"00:23:F8:7E:12:BB",
InfrastructureMode.NM_802_11_MODE_ADHOC,
2425,
5400,
82,
NM80211ApSecurityFlags.NM_802_11_AP_SEC_NONE,
)
self.dbusmock.AddAccessPoint(
wifi,
"Mock_AP3",
"AP_3",
"00:23:F8:7E:12:BC",
InfrastructureMode.NM_802_11_MODE_INFRA,
2425,
5400,
82,
NM80211ApSecurityFlags.NM_802_11_AP_SEC_KEY_MGMT_PSK,
)
out = self.read_device()
aps = self.read_device_wifi()
self.assertRegex(out, r"wlan0.*\sconnected")
self.assertRegex(aps, r"AP_1.*\sAd-Hoc")
self.assertRegex(aps, r"AP_3.*\sInfra")
# connect to non-existing wifi
res = subprocess.run(["nmcli", "dev", "wifi", "connect", "nonexisting"], check=False, stderr=subprocess.PIPE)
self.assertNotEqual(res.returncode, 0)
self.assertRegex(res.stderr, b"No network.*nonexisting")
self.assertRegex(self.read_device(), r"wlan0.*\sconnected\s+--")
# TODO: for connecting to password protected Wifi we need to implement secrets agent
# https://github.com/martinpitt/python-dbusmock/issues/216
out = subprocess.check_output(["nmcli", "--version"], text=True)
m = re.search(r"([1-9.]+[0-9])", out)
assert m, "could not parse version from " + out
if Version(m.group(1)) >= Version("1.49.3"):
self.skipTest("https://github.com/martinpitt/python-dbusmock/issues/216")
# connect to existing wifi with password
subprocess.check_call(
["timeout", "--signal=KILL", "5", "nmcli", "dev", "wifi", "connect", "AP_3", "password", "s3kr1t"]
)
self.assertRegex(self.read_device(), r"wlan0.*\sconnected\s+AP_3")
# connect to existing wifi without password
subprocess.check_call(["nmcli", "dev", "wifi", "connect", "AP_1"])
self.assertRegex(self.read_device(), r"wlan0.*\sconnected\s+AP_1")
def test_two_wifi_with_accesspoints(self):
wifi1 = self.dbusmock.AddWiFiDevice("mock_WiFi1", "wlan0", DeviceState.ACTIVATED)
wifi2 = self.dbusmock.AddWiFiDevice("mock_WiFi2", "wlan1", DeviceState.UNAVAILABLE)
self.dbusmock.AddAccessPoint(
wifi1,
"Mock_AP0",
"AP_0",
"00:23:F8:7E:12:BA",
InfrastructureMode.NM_802_11_MODE_UNKNOWN,
2425,
5400,
82,
0x400,
)
self.dbusmock.AddAccessPoint(
wifi2,
"Mock_AP1",
"AP_1",
"00:23:F8:7E:12:BB",
InfrastructureMode.NM_802_11_MODE_ADHOC,
2425,
5400,
82,
NM80211ApSecurityFlags.NM_802_11_AP_SEC_KEY_MGMT_PSK,
)
self.dbusmock.AddAccessPoint(
wifi2,
"Mock_AP3",
"AP_2",
"00:23:F8:7E:12:BC",
InfrastructureMode.NM_802_11_MODE_INFRA,
2425,
5400,
82,
0x400,
)
out = self.read_device()
aps = self.read_device_wifi()
self.assertRegex(out, r"wlan0.*\sconnected")
self.assertRegex(out, r"wlan1.*\sunavailable")
self.assertRegex(aps, r"AP_0.*\s(Unknown|N/A)")
self.assertRegex(aps, r"AP_1.*\sAd-Hoc")
self.assertRegex(aps, r"AP_2.*\sInfra")
def test_wifi_with_connection(self):
wifi1 = self.dbusmock.AddWiFiDevice("mock_WiFi1", "wlan0", DeviceState.ACTIVATED)
ap1 = self.dbusmock.AddAccessPoint(
wifi1,
"Mock_AP1",
"The_SSID",
"00:23:F8:7E:12:BB",
InfrastructureMode.NM_802_11_MODE_ADHOC,
2425,
5400,
82,
NM80211ApSecurityFlags.NM_802_11_AP_SEC_KEY_MGMT_PSK,
)
con1 = self.dbusmock.AddWiFiConnection(wifi1, "Mock_Con1", "The_SSID", "wpa-psk")
self.assertRegex(self.read_connection(), r"The_SSID.*\s(802-11-wireless|wifi)")
self.assertEqual(ap1, "/org/freedesktop/NetworkManager/AccessPoint/Mock_AP1")
self.assertEqual(con1, "/org/freedesktop/NetworkManager/Settings/Mock_Con1")
settings = subprocess.check_output(
["nmcli", "--nocheck", "connection", "show", "The_SSID"], env=self.lang_env, text=True
)
self.assertRegex(settings, r"ipv4.method:\s*auto")
self.assertRegex(settings, r"ipv4.gateway:\s*--")
self.assertRegex(settings, r"ipv6.method:\s*auto")
self.assertRegex(settings, r"ipv4.gateway:\s*--")
def test_global_state(self):
self.dbusmock.SetGlobalConnectionState(NMState.NM_STATE_CONNECTED_GLOBAL)
self.assertRegex(self.read_general(), r"connected.*\sfull")
self.dbusmock.SetGlobalConnectionState(NMState.NM_STATE_CONNECTED_SITE)
self.assertRegex(self.read_general(), r"connected \(site only\).*\sfull")
self.dbusmock.SetGlobalConnectionState(NMState.NM_STATE_CONNECTED_LOCAL)
self.assertRegex(self.read_general(), r"connected \(local only\).*\sfull")
self.dbusmock.SetGlobalConnectionState(NMState.NM_STATE_CONNECTING)
self.assertRegex(self.read_general(), r"connecting.*\sfull")
self.dbusmock.SetGlobalConnectionState(NMState.NM_STATE_DISCONNECTING)
self.assertRegex(self.read_general(), r"disconnecting.*\sfull")
self.dbusmock.SetGlobalConnectionState(NMState.NM_STATE_DISCONNECTED)
self.assertRegex(self.read_general(), r"disconnected.*\sfull")
self.dbusmock.SetGlobalConnectionState(NMState.NM_STATE_ASLEEP)
self.assertRegex(self.read_general(), r"asleep.*\sfull")
def test_connectivity_state(self):
self.dbusmock.SetConnectivity(NMConnectivityState.NM_CONNECTIVITY_FULL)
self.assertRegex(self.read_general(), r"connected.*\sfull")
self.dbusmock.SetConnectivity(NMConnectivityState.NM_CONNECTIVITY_LIMITED)
self.assertRegex(self.read_general(), r"connected.*\slimited")
self.dbusmock.SetConnectivity(NMConnectivityState.NM_CONNECTIVITY_PORTAL)
self.assertRegex(self.read_general(), r"connected.*\sportal")
self.dbusmock.SetConnectivity(NMConnectivityState.NM_CONNECTIVITY_NONE)
self.assertRegex(self.read_general(), r"connected.*\snone")
def test_networking(self):
self.dbusmock.SetNetworkingEnabled(False)
self.assertRegex(self.read_networking(), "disabled")
self.dbusmock.SetNetworkingEnabled(True)
self.assertRegex(self.read_networking(), "enabled")
def test_wifi_with_active_connection(self):
wifi1 = self.dbusmock.AddWiFiDevice("mock_WiFi1", "wlan0", DeviceState.ACTIVATED)
ap1 = self.dbusmock.AddAccessPoint(
wifi1,
"Mock_AP1",
"The_SSID",
"00:23:F8:7E:12:BB",
InfrastructureMode.NM_802_11_MODE_INFRA,
2425,
5400,
82,
NM80211ApSecurityFlags.NM_802_11_AP_SEC_KEY_MGMT_PSK,
)
con1 = self.dbusmock.AddWiFiConnection(wifi1, "Mock_Con1", "The_SSID", "")
active_con1 = self.dbusmock.AddActiveConnection(
[wifi1], con1, ap1, "Mock_Active1", NMActiveConnectionState.NM_ACTIVE_CONNECTION_STATE_ACTIVATED
)
self.assertEqual(ap1, "/org/freedesktop/NetworkManager/AccessPoint/Mock_AP1")
self.assertEqual(con1, "/org/freedesktop/NetworkManager/Settings/Mock_Con1")
self.assertEqual(active_con1, "/org/freedesktop/NetworkManager/ActiveConnection/Mock_Active1")
self.assertRegex(self.read_general(), r"connected.*\sfull")
self.assertRegex(self.read_connection(), r"The_SSID.*\s(802-11-wireless|wifi)")
self.assertRegex(self.read_active_connection(), r"The_SSID.*\s(802-11-wireless|wifi)")
self.assertRegex(self.read_device_wifi(), "The_SSID")
self.dbusmock.RemoveActiveConnection(wifi1, active_con1)
self.assertRegex(self.read_connection(), r"The_SSID.*\s(802-11-wireless|wifi)")
self.assertFalse(re.compile(r"The_SSID.*\s(802-11-wireless|wifi)").search(self.read_active_connection()))
self.assertRegex(self.read_device_wifi(), "The_SSID")
self.dbusmock.RemoveWifiConnection(wifi1, con1)
self.assertFalse(re.compile(r"The_SSID.*\s(802-11-wireless|wifi)").search(self.read_connection()))
self.assertRegex(self.read_device_wifi(), "The_SSID")
self.dbusmock.RemoveAccessPoint(wifi1, ap1)
self.assertFalse(re.compile(r"The_SSID").search(self.read_device_wifi()))
def test_add_connection(self):
self.dbusmock.AddWiFiDevice("mock_WiFi1", "wlan0", DeviceState.ACTIVATED)
uuid = "11111111-1111-1111-1111-111111111111"
settings = dbus.Dictionary(
{
"connection": dbus.Dictionary(
{"id": "test connection", "uuid": uuid, "type": "802-11-wireless"}, signature="sv"
),
"802-11-wireless": dbus.Dictionary({"ssid": dbus.ByteArray(b"The_SSID")}, signature="sv"),
},
signature="sa{sv}",
)
con1 = self.settings.AddConnection(settings)
self.assertEqual(con1, "/org/freedesktop/NetworkManager/Settings/0")
self.assertRegex(self.read_connection(), uuid + r".*\s(802-11-wireless|wifi)")
# Use the same settings, but this one will autoconnect.
uuid2 = "22222222-2222-2222-2222-222222222222"
settings["connection"]["autoconnect"] = dbus.Boolean(True)
settings["connection"]["uuid"] = uuid2
con2 = self.settings.AddConnection(settings)
self.assertEqual(con2, "/org/freedesktop/NetworkManager/Settings/1")
self.assertRegex(self.read_general(), r"connected.*\sfull")
self.assertRegex(self.read_connection(), uuid2 + r".*\s(802-11-wireless|wifi)")
self.assertRegex(self.read_active_connection(), uuid2 + r".*\s(802-11-wireless|wifi)")
def test_update_connection(self):
uuid = "133d8eb9-6de6-444f-8b37-f40bf9e33226"
settings = dbus.Dictionary(
{
"connection": dbus.Dictionary(
{"id": "test wireless", "uuid": uuid, "type": "802-11-wireless"}, signature="sv"
),
"802-11-wireless": dbus.Dictionary({"ssid": dbus.ByteArray(b"The_SSID")}, signature="sv"),
},
signature="sa{sv}",
)
con1 = self.settings.AddConnection(settings)
con1_iface = dbus.Interface(self.dbus_con.get_object(MANAGER_IFACE, con1), CSETTINGS_IFACE)
self.assertEqual(con1, "/org/freedesktop/NetworkManager/Settings/0")
self.assertRegex(self.read_connection(), uuid + r".*\s(802-11-wireless|wifi)")
new_settings = dbus.Dictionary(
{
"connection": dbus.Dictionary({"id": "test wired", "type": "802-3-ethernet"}, signature="sv"),
"802-3-ethernet": dbus.Dictionary({"name": "802-3-ethernet"}, signature="sv"),
},
signature="sa{sv}",
)
con1_iface.Update(new_settings)
self.assertRegex(self.read_connection(), uuid + r".*\s(ethernet|802-3-ethernet)")
def test_remove_connection(self):
wifi1 = self.dbusmock.AddWiFiDevice("mock_WiFi1", "wlan0", DeviceState.ACTIVATED)
ap1 = self.dbusmock.AddAccessPoint(
wifi1,
"Mock_AP1",
"The_SSID",
"00:23:F8:7E:12:BB",
InfrastructureMode.NM_802_11_MODE_INFRA,
2425,
5400,
82,
NM80211ApSecurityFlags.NM_802_11_AP_SEC_KEY_MGMT_PSK,
)
con1 = self.dbusmock.AddWiFiConnection(wifi1, "Mock_Con1", "The_SSID", "")
self.dbusmock.AddActiveConnection(
[wifi1], con1, ap1, "Mock_Active1", NMActiveConnectionState.NM_ACTIVE_CONNECTION_STATE_ACTIVATED
)
con1_i = dbus.Interface(self.dbus_con.get_object(MANAGER_IFACE, con1), CSETTINGS_IFACE)
con1_i.Delete()
self.assertRegex(self.read_general(), r"disconnected.*\sfull")
self.assertFalse(re.compile(r"The_SSID.*\s802-11-wireless").search(self.read_active_connection()))
self.assertRegex(self.read_device(), r"wlan0.*\sdisconnected")
def test_add_remove_settings(self):
connection = {
"connection": {
"timestamp": 1441979296,
"type": "vpn",
"id": "a",
"uuid": "11111111-1111-1111-1111-111111111111",
},
"vpn": {"service-type": "org.freedesktop.NetworkManager.openvpn", "data": {"connection-type": "tls"}},
"ipv4": {
"routes": dbus.Array([], signature="o"),
"never-default": True,
"addresses": dbus.Array([], signature="o"),
"dns": dbus.Array([], signature="o"),
"method": "auto",
},
"ipv6": {
"addresses": dbus.Array([], signature="o"),
"ip6-privacy": 0,
"dns": dbus.Array([], signature="o"),
"never-default": True,
"routes": dbus.Array([], signature="o"),
"method": "auto",
},
}
connectionA = self.settings.AddConnection(connection)
connection["connection"]["id"] = "b"
connection["connection"]["uuid"] = "11111111-1111-1111-1111-111111111112"
connectionB = self.settings.AddConnection(connection)
self.assertEqual(self.settings.ListConnections(), [connectionA, connectionB])
connectionA_i = dbus.Interface(self.dbus_con.get_object(MANAGER_IFACE, connectionA), CSETTINGS_IFACE)
connectionA_i.Delete()
self.assertEqual(self.settings.ListConnections(), [connectionB])
connection["connection"]["id"] = "c"
connection["connection"]["uuid"] = "11111111-1111-1111-1111-111111111113"
connectionC = self.settings.AddConnection(connection)
self.assertEqual(self.settings.ListConnections(), [connectionB, connectionC])
def test_add_update_settings(self):
connection = {
"connection": {
"timestamp": 1441979296,
"type": "vpn",
"id": "a",
"uuid": "11111111-1111-1111-1111-111111111111",
},
"vpn": {
"service-type": "org.freedesktop.NetworkManager.openvpn",
"data": dbus.Dictionary({"connection-type": "tls"}, signature="ss"),
},
"ipv4": {
"routes": dbus.Array([], signature="o"),
"never-default": True,
"addresses": dbus.Array([], signature="o"),
"dns": dbus.Array([], signature="o"),
"method": "auto",
},
"ipv6": {
"addresses": dbus.Array([], signature="o"),
"ip6-privacy": 0,
"dns": dbus.Array([], signature="o"),
"never-default": True,
"routes": dbus.Array([], signature="o"),
"method": "auto",
},
}
connectionA = self.settings.AddConnection(connection)
self.assertEqual(self.settings.ListConnections(), [connectionA])
connectionA_i = dbus.Interface(self.dbus_con.get_object(MANAGER_IFACE, connectionA), CSETTINGS_IFACE)
connection["connection"]["id"] = "b"
def do_update():
connectionA_i.Update(connection)
caught = []
ml = GLib.MainLoop()
def catch(*_, **kwargs):
if (
kwargs["interface"] == "org.freedesktop.NetworkManager.Settings.Connection"
and kwargs["member"] == "Updated"
):
caught.append(kwargs["path"])
ml.quit()
self.dbus_con.add_signal_receiver(
catch, interface_keyword="interface", path_keyword="path", member_keyword="member"
)
GLib.timeout_add(200, do_update)
# ensure that the loop quits even when we don't catch anything
GLib.timeout_add(3000, ml.quit)
ml.run()
self.assertEqual(connectionA_i.GetSettings(), connection)
self.assertEqual(caught, [connectionA])
def test_settings_secrets(self):
secrets = dbus.Dictionary(
{
"cert-pass": "certificate password",
"password": "the password",
},
signature="ss",
)
connection = {
"connection": {
"timestamp": 1441979296,
"type": "vpn",
"id": "a",
"uuid": "11111111-1111-1111-1111-111111111111",
},
"vpn": {
"service-type": "org.freedesktop.NetworkManager.openvpn",
"data": dbus.Dictionary(
{
"connection-type": "password-tls",
"remote": "remotey",
"ca": "/my/ca.crt",
"cert": "/my/cert.crt",
"cert-pass-flags": "1",
"key": "/my/key.key",
"password-flags": "1",
},
signature="ss",
),
"secrets": secrets,
},
"ipv4": {
"routes": dbus.Array([], signature="o"),
"never-default": True,
"addresses": dbus.Array([], signature="o"),
"dns": dbus.Array([], signature="o"),
"method": "auto",
},
"ipv6": {
"addresses": dbus.Array([], signature="o"),
"ip6-privacy": 0,
"dns": dbus.Array([], signature="o"),
"never-default": True,
"routes": dbus.Array([], signature="o"),
"method": "auto",
},
}
connectionPath = self.settings.AddConnection(connection)
self.assertEqual(self.settings.ListConnections(), [connectionPath])
connection_i = dbus.Interface(self.dbus_con.get_object(MANAGER_IFACE, connectionPath), CSETTINGS_IFACE)
# We expect there to be no secrets in the normal settings dict
del connection["vpn"]["secrets"]
self.assertEqual(connection_i.GetSettings(), connection)
# Secrets request should contain just vpn section with the secrets in
self.assertEqual(connection_i.GetSecrets("vpn"), {"vpn": {"secrets": secrets}})
def test_get_conn_by_uuid(self):
uuid = "133d8eb9-6de6-444f-8b37-f40bf9e33226"
settings = dbus.Dictionary(
{
"connection": dbus.Dictionary(
{"id": "test wireless", "uuid": uuid, "type": "802-11-wireless"}, signature="sv"
),
"802-11-wireless": dbus.Dictionary({"ssid": dbus.ByteArray(b"The_SSID")}, signature="sv"),
},
signature="sa{sv}",
)
connectionPath = self.settings.AddConnection(settings)
self.assertEqual(self.settings.GetConnectionByUuid(uuid), connectionPath)
with self.assertRaisesRegex(dbus.exceptions.DBusException, "No connection with the UUID was found."):
self.settings.GetConnectionByUuid("123123123213213")
if __name__ == "__main__":
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_notification_daemon.py 0000644 0001751 0001751 00000010611 15071377476 023264 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import fcntl
import os
import subprocess
import sys
import unittest
import dbus
import dbusmock
try:
notify_send_version = subprocess.check_output(["notify-send", "--version"], text=True)
notify_send_version = notify_send_version.split()[-1]
except (OSError, subprocess.CalledProcessError):
notify_send_version = ""
@unittest.skipUnless(notify_send_version, "notify-send not installed")
@unittest.skipIf(os.path.exists("/etc/apparmor.d/notify-send"), "AppArmor profile for notify-send active")
class TestNotificationDaemon(dbusmock.DBusTestCase):
"""Test mocking notification-daemon"""
@classmethod
def setUpClass(cls):
cls.start_session_bus()
cls.dbus_con = cls.get_dbus(False)
def setUp(self):
(self.p_mock, self.obj_daemon) = self.spawn_server_template("notification_daemon", {}, stdout=subprocess.PIPE)
# set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_no_options(self):
"""notify-send with no options"""
subprocess.check_call(["notify-send", "title", "my text"])
log = self.p_mock.stdout.read()
self.assertRegex(log, b'[0-9.]+ Notify "notify-send" 0 "" "title" "my text" \\[\\]')
def test_options(self):
"""notify-send with some options"""
subprocess.check_call(["notify-send", "-t", "27", "-a", "fooApp", "-i", "warning_icon", "title", "my text"])
log = self.p_mock.stdout.read()
# HACK: Why is the timeout missing on s390x?
if os.uname().machine != "s390x":
self.assertRegex(log, rb"[0-9.]+ Notify.* 27\n")
# libnotify 0.8.4 changes warning_icon from positional to optional argument, so check that separately
self.assertRegex(log, rb'[0-9.]+ Notify "fooApp" 0 "(warning_icon)?" "title" "my text" \[\] {.*"urgency": 1')
self.assertRegex(log, rb"[0-9.]+ Notify .*warning_icon")
def test_id(self):
"""ID handling"""
notify_proxy = dbus.Interface(
self.dbus_con.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications"),
"org.freedesktop.Notifications",
)
# with input ID 0 it should generate new IDs
id_ = notify_proxy.Notify("test", 0, "", "summary", "body", [], {}, -1)
self.assertEqual(id_, 1)
id_ = notify_proxy.Notify("test", 0, "", "summary", "body", [], {}, -1)
self.assertEqual(id_, 2)
# an existing ID should just be bounced back
id_ = notify_proxy.Notify("test", 4, "", "summary", "body", [], {}, -1)
self.assertEqual(id_, 4)
id_ = notify_proxy.Notify("test", 1, "", "summary", "body", [], {}, -1)
self.assertEqual(id_, 1)
# the previous doesn't forget the counter
id_ = notify_proxy.Notify("test", 0, "", "summary", "body", [], {}, -1)
self.assertEqual(id_, 3)
def test_close(self):
"""CloseNotification() and NotificationClosed() signal"""
notify_proxy = dbus.Interface(
self.dbus_con.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications"),
"org.freedesktop.Notifications",
)
id_ = notify_proxy.Notify("test", 0, "", "summary", "body", [], {}, -1)
self.assertEqual(id_, 1)
# known notification, should send a signal
notify_proxy.CloseNotification(id_)
log = self.p_mock.stdout.read()
self.assertRegex(log, b"[0-9.]+ emit .*NotificationClosed 1 1\n")
# unknown notification, don't send a signal
notify_proxy.CloseNotification(id_ + 1)
log = self.p_mock.stdout.read()
self.assertNotIn(b"NotificationClosed", log)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_ofono.py 0000644 0001751 0001751 00000014613 15071377476 020401 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import os
import subprocess
import sys
import unittest
from pathlib import Path
import dbus
import dbusmock
script_dir = Path(os.environ.get("OFONO_SCRIPT_DIR", "/usr/share/ofono/scripts"))
have_scripts = os.access(script_dir / "list-modems", os.X_OK)
@unittest.skipUnless(have_scripts, "ofono scripts not available, set $OFONO_SCRIPT_DIR")
class TestOfono(dbusmock.DBusTestCase):
"""Test mocking ofonod"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
(cls.p_mock, cls.obj_ofono) = cls.spawn_server_template("ofono", {}, stdout=subprocess.PIPE)
def setUp(self):
self.obj_ofono.Reset()
def test_list_modems(self):
"""Manager.GetModems()"""
out = subprocess.check_output([script_dir / "list-modems"])
self.assertTrue(out.startswith(b"[ /ril_0 ]"), out)
self.assertIn(b"Powered = 1", out)
self.assertIn(b"Online = 1", out)
self.assertIn(b"Model = Mock Modem", out)
self.assertIn(b"[ org.ofono.NetworkRegistration ]", out)
self.assertIn(b"Status = registered", out)
self.assertIn(b"Name = fake.tel", out)
self.assertIn(b"Technology = gsm", out)
self.assertIn(b"[ org.ofono.SimManager ]", out)
self.assertIn(b"PinRequired = none", out)
self.assertIn(b"Present = 1", out)
self.assertIn(b"CardIdentifier = 893581234000000000000", out)
self.assertIn(b"MobileCountryCode = 310", out)
self.assertIn(b"MobileNetworkCode = 150", out)
self.assertIn(b"Serial = 12345678-1234-1234-1234-000000000000", out)
self.assertIn(b"SubscriberIdentity = 310150000000000", out)
def test_outgoing_call(self):
"""outgoing voice call"""
# no calls by default
out = subprocess.check_output([script_dir / "list-calls"])
self.assertEqual(out, b"[ /ril_0 ]\n")
# start call
out = subprocess.check_output([script_dir / "dial-number", "12345"])
self.assertEqual(out, b"Using modem /ril_0\n/ril_0/voicecall01\n")
out = subprocess.check_output([script_dir / "list-calls"])
self.assertIn(b"/ril_0/voicecall01", out)
self.assertIn(b"LineIdentification = 12345", out)
self.assertIn(b"State = dialing", out)
out = subprocess.check_output([script_dir / "hangup-call", "/ril_0/voicecall01"])
self.assertEqual(out, b"")
# no active calls any more
out = subprocess.check_output([script_dir / "list-calls"])
self.assertEqual(out, b"[ /ril_0 ]\n")
def test_hangup_all(self):
"""multiple outgoing voice calls"""
out = subprocess.check_output([script_dir / "dial-number", "12345"])
self.assertEqual(out, b"Using modem /ril_0\n/ril_0/voicecall01\n")
out = subprocess.check_output([script_dir / "dial-number", "54321"])
self.assertEqual(out, b"Using modem /ril_0\n/ril_0/voicecall02\n")
out = subprocess.check_output([script_dir / "list-calls"])
self.assertIn(b"/ril_0/voicecall01", out)
self.assertIn(b"/ril_0/voicecall02", out)
self.assertIn(b"LineIdentification = 12345", out)
self.assertIn(b"LineIdentification = 54321", out)
out = subprocess.check_output([script_dir / "hangup-all"])
out = subprocess.check_output([script_dir / "list-calls"])
self.assertEqual(out, b"[ /ril_0 ]\n")
def test_list_operators(self):
"""list operators"""
out = subprocess.check_output([script_dir / "list-operators"], text=True)
self.assertTrue(out.startswith("[ /ril_0 ]"), out)
self.assertIn("[ /ril_0/operator/op1 ]", out)
self.assertIn("Status = current", out)
self.assertIn("Technologies = gsm", out)
self.assertIn("MobileNetworkCode = 11", out)
self.assertIn("MobileCountryCode = 777", out)
self.assertIn("Name = fake.tel", out)
def test_get_operators_for_two_modems(self):
"""Add second modem, list operators on both"""
iface = "org.ofono.NetworkRegistration"
# add second modem
self.obj_ofono.AddModem("sim2", {"Powered": True})
# get modem proxy, get netreg interface
modem_0 = self.dbus_con.get_object("org.ofono", "/ril_0")
modem_0_netreg = dbus.Interface(modem_0, dbus_interface=iface)
modem_0_ops = modem_0_netreg.GetOperators()
# get modem proxy, get netreg interface
modem_1 = self.dbus_con.get_object("org.ofono", "/sim2")
modem_1_netreg = dbus.Interface(modem_1, dbus_interface=iface)
modem_1_ops = modem_1_netreg.GetOperators()
self.assertIn("/ril_0/operator/op1", str(modem_0_ops))
self.assertNotIn("/sim2", str(modem_0_ops))
self.assertIn("/sim2/operator/op1", str(modem_1_ops))
self.assertNotIn("/ril_0", str(modem_1_ops))
def test_second_modem(self):
"""Add a second modem"""
out = subprocess.check_output([script_dir / "list-modems"])
self.assertIn(b"CardIdentifier = 893581234000000000000", out)
self.assertIn(b"Serial = 12345678-1234-1234-1234-000000000000", out)
self.assertIn(b"SubscriberIdentity = 310150000000000", out)
self.obj_ofono.AddModem("sim2", {"Powered": True})
out = subprocess.check_output([script_dir / "list-modems"])
self.assertTrue(out.startswith(b"[ /ril_0 ]"), out)
self.assertIn(b"[ /sim2 ]", out)
self.assertIn(b"Powered = 1", out)
self.assertIn(b"CardIdentifier = 893581234000000000000", out)
self.assertIn(b"Serial = 12345678-1234-1234-1234-000000000000", out)
self.assertIn(b"SubscriberIdentity = 310150000000000", out)
self.assertIn(b"CardIdentifier = 893581234000000000001", out)
self.assertIn(b"Serial = 12345678-1234-1234-1234-000000000001", out)
self.assertIn(b"SubscriberIdentity = 310150000000001", out)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_polkitd.py 0000644 0001751 0001751 00000011312 15071377476 020720 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import shutil
import subprocess
import sys
import unittest
import dbus
import dbus.mainloop.glib
import dbusmock
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
have_pkcheck = shutil.which("pkcheck")
@unittest.skipUnless(have_pkcheck, "pkcheck not installed")
class TestPolkit(dbusmock.DBusTestCase):
"""Test mocking polkitd"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
def setUp(self):
(self.p_mock, self.obj_polkitd) = self.spawn_server_template("polkitd", {}, stdout=subprocess.PIPE)
self.dbusmock = dbus.Interface(self.obj_polkitd, dbusmock.MOCK_IFACE)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_default(self):
self.check_action("org.freedesktop.test.frobnicate", False)
def test_allow_unknown(self):
self.dbusmock.AllowUnknown(True)
self.check_action("org.freedesktop.test.frobnicate", True)
self.dbusmock.AllowUnknown(False)
self.check_action("org.freedesktop.test.frobnicate", False)
def test_set_allowed(self):
self.dbusmock.SetAllowed(["org.freedesktop.test.frobnicate", "org.freedesktop.test.slap"])
self.check_action("org.freedesktop.test.frobnicate", True)
self.check_action("org.freedesktop.test.slap", True)
self.check_action("org.freedesktop.test.wobble", False)
def test_hanging_call(self):
self.dbusmock.SimulateHang(True)
self.assertFalse(self.dbusmock.HaveHangingCalls())
pkcheck = self.check_action_run("org.freedesktop.test.frobnicate")
with self.assertRaises(subprocess.TimeoutExpired):
pkcheck.wait(0.8)
self.assertTrue(self.dbusmock.HaveHangingCalls())
pkcheck.stdout.close()
pkcheck.kill()
pkcheck.wait()
def test_hanging_call_return(self):
self.dbusmock.SetAllowed(["org.freedesktop.test.frobnicate"])
self.dbusmock.SimulateHangActions(["org.freedesktop.test.frobnicate", "org.freedesktop.test.slap"])
self.assertFalse(self.dbusmock.HaveHangingCalls())
frobnicate_pkcheck = self.check_action_run("org.freedesktop.test.frobnicate")
slap_pkcheck = self.check_action_run("org.freedesktop.test.slap")
with self.assertRaises(subprocess.TimeoutExpired):
frobnicate_pkcheck.wait(0.3)
with self.assertRaises(subprocess.TimeoutExpired):
slap_pkcheck.wait(0.3)
self.assertTrue(self.dbusmock.HaveHangingCalls())
self.dbusmock.ReleaseHangingCalls()
self.check_action_result(frobnicate_pkcheck, True)
self.check_action_result(slap_pkcheck, False)
def test_delayed_call(self):
self.dbusmock.SetDelay(3)
pkcheck = self.check_action_run("org.freedesktop.test.frobnicate")
with self.assertRaises(subprocess.TimeoutExpired):
pkcheck.wait(0.8)
pkcheck.stdout.close()
pkcheck.kill()
pkcheck.wait()
def test_delayed_call_return(self):
self.dbusmock.SetDelay(1)
self.dbusmock.SetAllowed(["org.freedesktop.test.frobnicate"])
pkcheck = self.check_action_run("org.freedesktop.test.frobnicate")
with self.assertRaises(subprocess.TimeoutExpired):
pkcheck.wait(0.8)
self.check_action_result(pkcheck, True)
@staticmethod
def check_action_run(action):
# pylint: disable=consider-using-with
return subprocess.Popen(
["pkcheck", "--action-id", action, "--process", "123"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
)
def check_action_result(self, pkcheck, expect_allow):
out = pkcheck.communicate()[0]
if expect_allow:
self.assertEqual(pkcheck.returncode, 0)
self.assertEqual(out, "test=test\n")
else:
self.assertNotEqual(pkcheck.returncode, 0)
self.assertEqual(out, "test=test\nNot authorized.\n")
def check_action(self, action, expect_allow):
self.check_action_result(self.check_action_run(action), expect_allow)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_power_profiles_daemon.py 0000644 0001751 0001751 00000014176 15071377476 023647 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Bastien Nocera"
__copyright__ = """
(c) 2021 Red Hat Inc.
(c) 2017 - 2022 Martin Pitt
"""
import fcntl
import os
import re
import shutil
import subprocess
import sys
import time
import unittest
import dbus
import dbus.mainloop.glib
import dbusmock
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
have_powerprofilesctl = shutil.which("powerprofilesctl")
@unittest.skipUnless(have_powerprofilesctl, "powerprofilesctl not installed")
class TestPowerProfilesDaemon(dbusmock.DBusTestCase):
"""Test mocking power-profiles-daemon"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
def setUp(self):
# depending on the installed client version, we need to pick the right template
try:
out = subprocess.run(["powerprofilesctl", "version"], capture_output=True, text=True, check=True).stdout
version = re.search(r"[0-9.]+", out).group(0)
version = ".".join(version.strip().split(".")[:2])
template = "power_profiles_daemon" if float(version) < 0.2 else "upower_power_profiles_daemon"
except subprocess.CalledProcessError as e:
# 0.20 crashes without daemon: https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/issues/139
print("Failed to get powerprofilesctl version, assuming >= 0.20:", e, file=sys.stderr)
template = "upower_power_profiles_daemon"
(self.p_mock, self.obj_ppd) = self.spawn_server_template(template, {}, stdout=subprocess.PIPE)
# set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.dbusmock = dbus.Interface(self.obj_ppd, dbusmock.MOCK_IFACE)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_list_profiles(self):
"""List Profiles and check active profile"""
out = subprocess.check_output(["powerprofilesctl"], text=True)
self.assertIn("performance:\n", out)
self.assertIn("\n* balanced:\n", out)
def test_change_profile(self):
"""Change ActiveProfile"""
subprocess.check_output(["powerprofilesctl", "set", "performance"], text=True)
out = subprocess.check_output(["powerprofilesctl", "get"], text=True)
self.assertEqual(out, "performance\n")
def run_powerprofilesctl_list_holds(self):
return subprocess.check_output(["powerprofilesctl", "list-holds"], text=True)
def test_list_holds(self):
"""Test holds"""
# No holds
out = self.run_powerprofilesctl_list_holds()
self.assertEqual(out, "")
# 1 hold
# pylint: disable=consider-using-with
cmd = subprocess.Popen(
[
"powerprofilesctl",
"launch",
"-p",
"power-saver",
"-r",
"g-s-d mock test",
"-i",
"org.gnome.SettingsDaemon.Power",
"sleep",
"60",
],
stdout=subprocess.PIPE,
)
time.sleep(0.3)
out = self.run_powerprofilesctl_list_holds()
self.assertEqual(
out,
(
"Hold:\n"
" Profile: power-saver\n"
" Application ID:"
" org.gnome.SettingsDaemon.Power\n"
" Reason: g-s-d mock test\n"
),
)
# 2 holds
# pylint: disable=consider-using-with
cmd2 = subprocess.Popen(
[
"powerprofilesctl",
"launch",
"-p",
"performance",
"-r",
"running some game",
"-i",
"com.game.Game",
"sleep",
"60",
],
stdout=subprocess.PIPE,
)
out = None
timeout = 2.0
while timeout > 0:
time.sleep(0.1)
timeout -= 0.1
out = self.run_powerprofilesctl_list_holds()
if out != "":
break
else:
self.fail("could not list holds")
self.assertEqual(
out,
(
"Hold:\n"
" Profile: power-saver\n"
" Application ID:"
" org.gnome.SettingsDaemon.Power\n"
" Reason: g-s-d mock test\n\n"
"Hold:\n"
" Profile: performance\n"
" Application ID: com.game.Game\n"
" Reason: running some game\n"
),
)
cmd.stdout.close()
cmd.terminate()
cmd.wait()
cmd2.stdout.close()
cmd2.terminate()
cmd2.wait()
def test_release_hold(self):
"""Test release holds"""
# No holds
out = self.run_powerprofilesctl_list_holds()
self.assertEqual(out, "")
# hold profile
cookie = self.obj_ppd.HoldProfile("performance", "release test", "com.test.Test")
out = self.run_powerprofilesctl_list_holds()
self.assertEqual(
out,
(
"Hold:\n"
" Profile: performance\n"
" Application ID: com.test.Test\n"
" Reason: release test\n"
),
)
# release profile
self.obj_ppd.ReleaseProfile(cookie)
time.sleep(0.3)
out = self.run_powerprofilesctl_list_holds()
self.assertEqual(out, "")
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_systemd.py 0000644 0001751 0001751 00000006460 15071377476 020752 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Jonas Ådahl"
__copyright__ = """
(c) 2021 Red Hat
(c) 2017 - 2022 Martin Pitt
"""
import subprocess
import sys
import unittest
import dbus
import dbus.mainloop.glib
from gi.repository import GLib
import dbusmock
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
class TestSystemd(dbusmock.DBusTestCase):
"""Test mocking systemd"""
@classmethod
def setUpClass(cls):
cls.start_session_bus()
cls.start_system_bus()
cls.session_bus = cls.get_dbus(False)
cls.system_bus = cls.get_dbus(True)
def setUp(self):
self.p_mock = None
def tearDown(self):
if self.p_mock:
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def _assert_unit_property(self, unit_obj, name, expect):
value = unit_obj.Get("org.freedesktop.systemd1.Unit", name)
self.assertEqual(str(value), expect)
def _test_base(self, bus, system_bus=True):
dummy_service = "dummy-dbusmock.service"
(self.p_mock, obj_systemd) = self.spawn_server_template("systemd", {}, subprocess.PIPE, system_bus=system_bus)
systemd_mock = dbus.Interface(obj_systemd, dbusmock.MOCK_IFACE)
systemd_mock.AddMockUnit(dummy_service)
main_loop = GLib.MainLoop()
removed_jobs = []
def catch_job_removed(*args, **kwargs):
if kwargs["interface"] == "org.freedesktop.systemd1.Manager" and kwargs["member"] == "JobRemoved":
job_path = str(args[1])
removed_jobs.append(job_path)
main_loop.quit()
def wait_for_job(path):
while True:
main_loop.run()
if path in removed_jobs:
break
bus.add_signal_receiver(
catch_job_removed, interface_keyword="interface", path_keyword="path", member_keyword="member"
)
unit_path = obj_systemd.GetUnit(dummy_service)
unit_obj = bus.get_object("org.freedesktop.systemd1", unit_path)
self._assert_unit_property(unit_obj, "Id", dummy_service)
self._assert_unit_property(unit_obj, "LoadState", "loaded")
self._assert_unit_property(unit_obj, "ActiveState", "inactive")
job_path = obj_systemd.StartUnit(dummy_service, "fail")
wait_for_job(job_path)
self._assert_unit_property(unit_obj, "ActiveState", "active")
job_path = obj_systemd.StopUnit(dummy_service, "fail")
wait_for_job(job_path)
self._assert_unit_property(unit_obj, "ActiveState", "inactive")
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
self.p_mock = None
def test_user(self):
self._test_base(self.session_bus, system_bus=False)
def test_system(self):
self._test_base(self.system_bus, system_bus=True)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_timedated.py 0000644 0001751 0001751 00000005525 15071377476 021223 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Iain Lane"
__copyright__ = """
(c) 2013 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import shutil
import subprocess
import sys
import unittest
from pathlib import Path
import dbusmock
# timedatectl keeps changing its CLI output
TIMEDATECTL_NTP_LABEL = "(NTP enabled|synchronized|systemd-timesyncd.service active)"
have_timedatectl = shutil.which("timedatectl")
@unittest.skipUnless(have_timedatectl, "timedatectl not installed")
@unittest.skipUnless(Path("/run/systemd/system").exists(), "/run/systemd/system does not exist")
class TestTimedated(dbusmock.DBusTestCase):
"""Test mocking timedated"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
def setUp(self):
(self.p_mock, _) = self.spawn_server_template("timedated", {}, stdout=subprocess.PIPE)
self.obj_timedated = self.dbus_con.get_object("org.freedesktop.timedate1", "/org/freedesktop/timedate1")
def tearDown(self):
if self.p_mock:
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def run_timedatectl(self):
return subprocess.check_output(["timedatectl"], text=True)
def test_default_timezone(self):
out = self.run_timedatectl()
# timedatectl doesn't get the timezone offset information over dbus so
# we can't mock that.
self.assertRegex(out, "Time *zone: Etc/Utc")
def test_changing_timezone(self):
self.obj_timedated.SetTimezone("Africa/Johannesburg", False)
out = self.run_timedatectl()
# timedatectl doesn't get the timezone offset information over dbus so
# we can't mock that.
self.assertRegex(out, "Time *zone: Africa/Johannesburg")
def test_default_ntp(self):
out = self.run_timedatectl()
self.assertRegex(out, f"{TIMEDATECTL_NTP_LABEL}: yes")
def test_changing_ntp(self):
self.obj_timedated.SetNTP(False, False)
out = self.run_timedatectl()
self.assertRegex(out, f"{TIMEDATECTL_NTP_LABEL}: no")
def test_default_local_rtc(self):
out = self.run_timedatectl()
self.assertRegex(out, "RTC in local TZ: no")
def test_changing_local_rtc(self):
self.obj_timedated.SetLocalRTC(True, False, False)
out = self.run_timedatectl()
self.assertRegex(out, "RTC in local TZ: yes")
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_upower.py 0000644 0001751 0001751 00000020052 15071377476 020574 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Martin Pitt"
__copyright__ = """
(c) 2012 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import fcntl
import os
import shutil
import subprocess
import sys
import time
import tracemalloc
import unittest
import dbus
import dbusmock
UP_DEVICE_LEVEL_UNKNOWN = 0
UP_DEVICE_LEVEL_NONE = 1
tracemalloc.start(25)
have_upower = shutil.which("upower")
@unittest.skipUnless(have_upower, "upower not installed")
class TestUPower(dbusmock.DBusTestCase):
"""Test mocking upowerd"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
def setUp(self):
(self.p_mock, self.obj_upower) = self.spawn_server_template(
"upower",
{
"OnBattery": True,
"HibernateAllowed": False,
"GetCriticalAction": "Suspend",
},
stdout=subprocess.PIPE,
)
# set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.dbusmock = dbus.Interface(self.obj_upower, dbusmock.MOCK_IFACE)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_no_devices(self):
out = subprocess.check_output(["upower", "--dump"], text=True)
self.assertIn("/DisplayDevice\n", out)
# should not have any other device
for line in out.splitlines():
if line.endswith("/DisplayDevice"):
continue
self.assertNotIn("Device", line)
self.assertRegex(out, "on-battery:\\s+yes")
self.assertRegex(out, "lid-is-present:\\s+yes")
self.assertRegex(out, "daemon-version:\\s+0.99")
self.assertRegex(out, "critical-action:\\s+Suspend")
self.assertNotIn("can-suspend", out)
def test_one_ac(self):
path = self.dbusmock.AddAC("mock_AC", "Mock AC")
self.assertEqual(path, "/org/freedesktop/UPower/devices/mock_AC")
self.assertRegex(
self.p_mock.stdout.read(),
b'emit /org/freedesktop/UPower org.freedesktop.UPower.DeviceAdded "/org/freedesktop/UPower/devices/mock_AC"\n',
)
out = subprocess.check_output(["upower", "--dump"], text=True)
self.assertRegex(out, "Device: " + path)
# note, Add* is not magic: this just adds an object, not change
# properties
self.assertRegex(out, "on-battery:\\s+yes")
self.assertRegex(out, "lid-is-present:\\s+yes")
# print('--------- out --------\n%s\n------------' % out)
with subprocess.Popen(["upower", "--monitor-detail"], stdout=subprocess.PIPE, universal_newlines=True) as mon:
time.sleep(0.3)
self.dbusmock.SetDeviceProperties(path, {"PowerSupply": dbus.Boolean(True)})
time.sleep(0.2)
mon.terminate()
out = mon.communicate()[0]
self.assertRegex(out, "device changed:\\s+" + path)
# print('--------- monitor out --------\n%s\n------------' % out)
def test_discharging_battery(self):
path = self.dbusmock.AddDischargingBattery("mock_BAT", "Mock Battery", 30.0, 1200)
self.assertEqual(path, "/org/freedesktop/UPower/devices/mock_BAT")
self.assertRegex(
self.p_mock.stdout.read(),
b'emit /org/freedesktop/UPower org.freedesktop.UPower.DeviceAdded "/org/freedesktop/UPower/devices/mock_BAT"\n',
)
out = subprocess.check_output(["upower", "--dump"], text=True)
self.assertRegex(out, "Device: " + path)
# note, Add* is not magic: this just adds an object, not change
# properties
self.assertRegex(out, "on-battery:\\s+yes")
self.assertRegex(out, "lid-is-present:\\s+yes")
self.assertRegex(out, " present:\\s+yes")
self.assertRegex(out, " percentage:\\s+30%")
self.assertRegex(out, " time to empty:\\s+20.0 min")
self.assertRegex(out, " state:\\s+discharging")
def test_charging_battery(self):
path = self.dbusmock.AddChargingBattery("mock_BAT", "Mock Battery", 30.0, 1200)
self.assertEqual(path, "/org/freedesktop/UPower/devices/mock_BAT")
self.assertRegex(
self.p_mock.stdout.read(),
b'emit /org/freedesktop/UPower org.freedesktop.UPower.DeviceAdded "/org/freedesktop/UPower/devices/mock_BAT"\n',
)
out = subprocess.check_output(["upower", "--dump"], text=True)
self.assertRegex(out, "Device: " + path)
# note, Add* is not magic: this just adds an object, not change
# properties
self.assertRegex(out, "on-battery:\\s+yes")
self.assertRegex(out, "lid-is-present:\\s+yes")
self.assertRegex(out, " present:\\s+yes")
self.assertRegex(out, " percentage:\\s+30%")
self.assertRegex(out, " time to full:\\s+20.0 min")
self.assertRegex(out, " state:\\s+charging")
def test_enumerate(self):
self.dbusmock.AddAC("mock_AC", "Mock AC")
self.assertEqual(self.obj_upower.EnumerateDevices(), ["/org/freedesktop/UPower/devices/mock_AC"])
def test_display_device_default(self):
path = self.obj_upower.GetDisplayDevice()
self.assertEqual(path, "/org/freedesktop/UPower/devices/DisplayDevice")
display_dev = self.dbus_con.get_object("org.freedesktop.UPower", path)
props = display_dev.GetAll("org.freedesktop.UPower.Device")
# http://cgit.freedesktop.org/upower/tree/src/org.freedesktop.UPower.xml
# defines the properties which are defined
self.assertEqual(
set(props.keys()),
{
"Type",
"State",
"Percentage",
"Energy",
"EnergyFull",
"EnergyRate",
"TimeToEmpty",
"TimeToFull",
"IsPresent",
"IconName",
"WarningLevel",
},
)
# not set up by default, so should not present
self.assertEqual(props["IsPresent"], False)
self.assertEqual(props["IconName"], "")
self.assertEqual(props["WarningLevel"], UP_DEVICE_LEVEL_NONE)
def test_setup_display_device(self):
self.dbusmock.SetupDisplayDevice(2, 1, 50.0, 40.0, 80.0, 2.5, 3600, 1800, True, "half-battery", 3)
path = self.obj_upower.GetDisplayDevice()
display_dev = self.dbus_con.get_object("org.freedesktop.UPower", path)
props = display_dev.GetAll("org.freedesktop.UPower.Device")
# just some spot-checks, check all the values from upower -d
self.assertEqual(props["Type"], 2)
self.assertEqual(props["Percentage"], 50.0)
self.assertEqual(props["WarningLevel"], 3)
env = os.environ.copy()
env["LC_ALL"] = "C"
try:
del env["LANGUAGE"]
except KeyError:
pass
out = subprocess.check_output(["upower", "--dump"], text=True, env=env)
self.assertIn("/DisplayDevice\n", out)
self.assertIn(" battery\n", out) # type
self.assertRegex(out, r"state:\s+charging")
self.assertRegex(out, r"percentage:\s+50%")
self.assertRegex(out, r"energy:\s+40 Wh")
self.assertRegex(out, r"energy-full:\s+80 Wh")
self.assertRegex(out, r"energy-rate:\s+2.5 W")
self.assertRegex(out, r"time to empty:\s+1\.0 hours")
self.assertRegex(out, r"time to full:\s+30\.0 minutes")
self.assertRegex(out, r"present:\s+yes")
self.assertRegex(out, r"icon-name:\s+'half-battery'")
self.assertRegex(out, r"warning-level:\s+low")
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1759903550.0
python_dbusmock-0.37.1/tests/test_urfkill.py 0000644 0001751 0001751 00000011553 15071377476 020731 0 ustar 00runner runner # This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = "Jussi Pakkanen"
__copyright__ = """
(c) 2015 Canonical Ltd.
(c) 2017 - 2022 Martin Pitt
"""
import fcntl
import os
import subprocess
import sys
import unittest
import dbus
import dbusmock
def _get_urfkill_objects():
bus = dbus.SystemBus()
remote_object = bus.get_object("org.freedesktop.URfkill", "/org/freedesktop/URfkill")
iface = dbus.Interface(remote_object, "org.freedesktop.URfkill")
return (remote_object, iface)
class TestURfkill(dbusmock.DBusTestCase):
"""Test mocked URfkill"""
@classmethod
def setUpClass(cls):
cls.start_system_bus()
cls.dbus_con = cls.get_dbus(True)
def setUp(self):
(self.p_mock, self.obj_urfkill) = self.spawn_server_template("urfkill", {}, stdout=subprocess.PIPE)
# set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.dbusmock = dbus.Interface(self.obj_urfkill, dbusmock.MOCK_IFACE)
def tearDown(self):
self.p_mock.stdout.close()
self.p_mock.terminate()
self.p_mock.wait()
def test_mainobject(self):
(remote_object, iface) = _get_urfkill_objects()
self.assertFalse(iface.IsFlightMode())
propiface = dbus.Interface(remote_object, dbus.PROPERTIES_IFACE)
version = propiface.Get("org.freedesktop.URfkill", "DaemonVersion")
self.assertEqual(version, "0.6.0")
def test_subobjects(self):
bus = dbus.SystemBus()
individual_objects = ["BLUETOOTH", "FM", "GPS", "NFC", "UWB", "WIMAX", "WLAN", "WWAN"]
for i in individual_objects:
path = "/org/freedesktop/URfkill/" + i
remote_object = bus.get_object("org.freedesktop.URfkill", path)
propiface = dbus.Interface(remote_object, dbus.PROPERTIES_IFACE)
state = propiface.Get("org.freedesktop.URfkill.Killswitch", "state")
self.assertEqual(state, 0)
def test_block(self):
bus = dbus.SystemBus()
(_, iface) = _get_urfkill_objects()
property_object = bus.get_object("org.freedesktop.URfkill", "/org/freedesktop/URfkill/WLAN")
propiface = dbus.Interface(property_object, dbus.PROPERTIES_IFACE)
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0)
self.assertTrue(iface.Block(1, True))
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1)
self.assertTrue(iface.Block(1, False))
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0)
# 99 is an unknown type to the mock, so it should return false.
self.assertFalse(iface.Block(99, False))
def test_flightmode(self):
bus = dbus.SystemBus()
(_, iface) = _get_urfkill_objects()
property_object = bus.get_object("org.freedesktop.URfkill", "/org/freedesktop/URfkill/WLAN")
propiface = dbus.Interface(property_object, dbus.PROPERTIES_IFACE)
self.assertFalse(iface.IsFlightMode())
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0)
iface.FlightMode(True)
self.assertTrue(iface.IsFlightMode())
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1)
iface.FlightMode(False)
self.assertFalse(iface.IsFlightMode())
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0)
def test_flightmode_restore(self):
# An interface that was blocked remains blocked once flightmode is removed.
bus = dbus.SystemBus()
(_, iface) = _get_urfkill_objects()
property_object = bus.get_object("org.freedesktop.URfkill", "/org/freedesktop/URfkill/WLAN")
propiface = dbus.Interface(property_object, dbus.PROPERTIES_IFACE)
self.assertFalse(iface.IsFlightMode())
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 0)
iface.Block(1, True)
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1)
iface.FlightMode(True)
self.assertTrue(iface.IsFlightMode())
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1)
iface.FlightMode(False)
self.assertFalse(iface.IsFlightMode())
self.assertEqual(propiface.Get("org.freedesktop.URfkill.Killswitch", "state"), 1)
if __name__ == "__main__":
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))