Sun Feb 2 06:47:00 UTC 2025 I: starting to build errbot/trixie/i386 on jenkins on '2025-02-02 06:46' Sun Feb 2 06:47:00 UTC 2025 I: The jenkins build log is/was available at https://jenkins.debian.net/userContent/reproducible/debian/build_service/i386_9/42557/console.log Sun Feb 2 06:47:00 UTC 2025 I: Downloading source for trixie/errbot=6.2.0+ds-4 --2025-02-02 06:47:00-- http://deb.debian.org/debian/pool/main/e/errbot/errbot_6.2.0%2bds-4.dsc Connecting to 46.16.76.132:3128... connected. Proxy request sent, awaiting response... 200 OK Length: 2233 (2.2K) [text/prs.lines.tag] Saving to: ‘errbot_6.2.0+ds-4.dsc’ 0K .. 100% 351M=0s 2025-02-02 06:47:00 (351 MB/s) - ‘errbot_6.2.0+ds-4.dsc’ saved [2233/2233] Sun Feb 2 06:47:00 UTC 2025 I: errbot_6.2.0+ds-4.dsc -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Format: 3.0 (quilt) Source: errbot Binary: errbot Architecture: all Version: 6.2.0+ds-4 Maintainer: Debian Python Team Uploaders: Birger Schacht Homepage: http://errbot.io Standards-Version: 4.5.1 Vcs-Browser: https://salsa.debian.org/python-team/packages/errbot Vcs-Git: https://salsa.debian.org/python-team/packages/errbot.git Build-Depends: debhelper-compat (= 13), dh-sequence-python3, git-core, localehelper, python3-all, python3-ansi, python3-bottle, python3-colorlog, python3-daemonize, python3-flask, python3-jinja2, python3-markdown, python3-openssl, python3-pygments, python3-pytest, python3-requests, python3-setuptools, python3-webtest, python3-yapsy, python3-dulwich Package-List: errbot deb net optional arch=all Checksums-Sha1: b23eaa6a5b0e436861b433da2aededefa754985c 1341096 errbot_6.2.0+ds.orig.tar.xz 671fe4358d85bd1bdd6472259fb36f410f654fb5 7412 errbot_6.2.0+ds-4.debian.tar.xz Checksums-Sha256: 5a9bd3ce367906518960493f56e2c547047435c02581d4e3cef5c5f6255cb18a 1341096 errbot_6.2.0+ds.orig.tar.xz be45ae630c90777d33da6b3d986488f103e11add0edd99f6ff4247e10cfd3074 7412 errbot_6.2.0+ds-4.debian.tar.xz Files: 8c91bbd9f785227ef8a1747e464fa86e 1341096 errbot_6.2.0+ds.orig.tar.xz 7b3d51bb8099646560ee85ef0264b860 7412 errbot_6.2.0+ds-4.debian.tar.xz -----BEGIN PGP SIGNATURE----- iQJFBAEBCgAvFiEEj23hBDd/OxHnQXSHMfMURUShdBoFAmeYDGgRHHRjaGV0QGRl Ymlhbi5vcmcACgkQMfMURUShdBpl1RAAkimq/urVrSs0L6HP0k8D6sULn9gL8zIb q+b47y2bDTUoa4Lgomfc9LR3WrQFhUz083W0eE6S+1/qCz7pePHCLN8qk6iiZwrb J/l0PTPsaVCKq1Rsq/S/ObQzc4QfBu+UgErVjQHpp5FrTEucDHYfTaWEvKbYHly9 TshAlKcLLPY9SZ86BU05emnKHK4vzSxD70Ehxi/BrE6joSh2WU+hm1OGPy738y9p PdEEyYjfN3dyMGtarqStVX3P81t/QRmKYW9fY1rTvg6eaSoIgPVKKrOw2NhGZ/z8 OOkuEWus2S6AVemBY1wZOZjJNlEJaXQFBnS+MIUaLbRww976IGU9Qh+OtpHoOxdW j5gO0hLO3iNWiD4kE9k8enSPKQpQ4D2cgvQAqP56AGVb6+ZyG/60gxKP4TCClgFQ ykylgEuKRqe1lXTaenvw5ht/j/z/4O4AbtCY8KfSESTfcfK4hY6q9ZgbIJaB34GO aQdyLqBT1wa1hmX43TXAgS/vRgY2ZIahAb7/8u2Cn32ZKlQDvD5qiaKGchay6iFu yK1gHTCHVrTc9NmP2i9h4gOf0SViD7IgdCe/o4AI+JgaPiUdMwddCwqB11/FFUm9 mmCAWIxlbo8dVjst+aEr0yukzVsIfZef4txZ6h5g8CS+NSef2F8yUDSkYZc5aFoM uIlsRpfFl0o= =e8nH -----END PGP SIGNATURE----- Sun Feb 2 06:47:00 UTC 2025 I: Checking whether the package is not for us Sun Feb 2 06:47:00 UTC 2025 I: Starting 1st build on remote node ionos2-i386.debian.net. Sun Feb 2 06:47:00 UTC 2025 I: Preparing to do remote build '1' on ionos2-i386.debian.net. Sun Feb 2 06:49:45 UTC 2025 I: Deleting $TMPDIR on ionos2-i386.debian.net. I: pbuilder: network access will be disabled during build I: Current time: Sat Feb 1 18:47:07 -12 2025 I: pbuilder-time-stamp: 1738478827 I: Building the build Environment I: extracting base tarball [/var/cache/pbuilder/trixie-reproducible-base.tgz] I: copying local configuration W: --override-config is not set; not updating apt.conf Read the manpage for details. I: mounting /proc filesystem I: mounting /sys filesystem I: creating /{dev,run}/shm I: mounting /dev/pts filesystem I: redirecting /dev/ptmx to /dev/pts/ptmx I: policy-rc.d already exists I: using eatmydata during job I: Copying source file I: copying [errbot_6.2.0+ds-4.dsc] I: copying [./errbot_6.2.0+ds.orig.tar.xz] I: copying [./errbot_6.2.0+ds-4.debian.tar.xz] I: Extracting source dpkg-source: warning: cannot verify inline signature for ./errbot_6.2.0+ds-4.dsc: unsupported subcommand dpkg-source: info: extracting errbot in errbot-6.2.0+ds dpkg-source: info: unpacking errbot_6.2.0+ds.orig.tar.xz dpkg-source: info: unpacking errbot_6.2.0+ds-4.debian.tar.xz dpkg-source: info: using patch list from debian/patches/series dpkg-source: info: applying 0002-Remove-pygments-markdown-lexer-dependency.patch dpkg-source: info: applying SyntaxWarning.patch I: Not using root during the build. I: Installing the build-deps I: user script /srv/workspace/pbuilder/41078/tmp/hooks/D02_print_environment starting I: set BUILDDIR='/build/reproducible-path' BUILDUSERGECOS='first user,first room,first work-phone,first home-phone,first other' BUILDUSERNAME='pbuilder1' BUILD_ARCH='i386' DEBIAN_FRONTEND='noninteractive' DEB_BUILD_OPTIONS='buildinfo=+all reproducible=+all parallel=11 ' DISTRIBUTION='trixie' HOME='/root' HOST_ARCH='i386' IFS=' ' INVOCATION_ID='bdf98ee7dbbf494085ac683ce51c2a0b' LANG='C' LANGUAGE='en_US:en' LC_ALL='C' LD_LIBRARY_PATH='/usr/lib/libeatmydata' LD_PRELOAD='libeatmydata.so' MAIL='/var/mail/root' OPTIND='1' PATH='/usr/sbin:/usr/bin:/sbin:/bin:/usr/games' PBCURRENTCOMMANDLINEOPERATION='build' PBUILDER_OPERATION='build' PBUILDER_PKGDATADIR='/usr/share/pbuilder' PBUILDER_PKGLIBDIR='/usr/lib/pbuilder' PBUILDER_SYSCONFDIR='/etc' PPID='41078' PS1='# ' PS2='> ' PS4='+ ' PWD='/' SHELL='/bin/bash' SHLVL='2' SUDO_COMMAND='/usr/bin/timeout -k 18.1h 18h /usr/bin/ionice -c 3 /usr/bin/nice /usr/sbin/pbuilder --build --configfile /srv/reproducible-results/rbuild-debian/r-b-build.P8Ks7RAp/pbuilderrc_sET0 --distribution trixie --hookdir /etc/pbuilder/first-build-hooks --debbuildopts -b --basetgz /var/cache/pbuilder/trixie-reproducible-base.tgz --buildresult /srv/reproducible-results/rbuild-debian/r-b-build.P8Ks7RAp/b1 --logfile b1/build.log errbot_6.2.0+ds-4.dsc' SUDO_GID='112' SUDO_UID='107' SUDO_USER='jenkins' TERM='unknown' TZ='/usr/share/zoneinfo/Etc/GMT+12' USER='root' _='/usr/bin/systemd-run' http_proxy='http://46.16.76.132:3128' I: uname -a Linux ionos2-i386 6.1.0-30-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.124-1 (2025-01-12) x86_64 GNU/Linux I: ls -l /bin lrwxrwxrwx 1 root root 7 Nov 22 14:40 /bin -> usr/bin I: user script /srv/workspace/pbuilder/41078/tmp/hooks/D02_print_environment finished -> Attempting to satisfy build-dependencies -> Creating pbuilder-satisfydepends-dummy package Package: pbuilder-satisfydepends-dummy Version: 0.invalid.0 Architecture: i386 Maintainer: Debian Pbuilder Team Description: Dummy package to satisfy dependencies with aptitude - created by pbuilder This package was created automatically by pbuilder to satisfy the build-dependencies of the package being currently built. Depends: debhelper-compat (= 13), dh-sequence-python3, git-core, localehelper, python3-all, python3-ansi, python3-bottle, python3-colorlog, python3-daemonize, python3-flask, python3-jinja2, python3-markdown, python3-openssl, python3-pygments, python3-pytest, python3-requests, python3-setuptools, python3-webtest, python3-yapsy, python3-dulwich dpkg-deb: building package 'pbuilder-satisfydepends-dummy' in '/tmp/satisfydepends-aptitude/pbuilder-satisfydepends-dummy.deb'. Selecting previously unselected package pbuilder-satisfydepends-dummy. (Reading database ... 19834 files and directories currently installed.) Preparing to unpack .../pbuilder-satisfydepends-dummy.deb ... Unpacking pbuilder-satisfydepends-dummy (0.invalid.0) ... dpkg: pbuilder-satisfydepends-dummy: dependency problems, but configuring anyway as you requested: pbuilder-satisfydepends-dummy depends on debhelper-compat (= 13); however: Package debhelper-compat is not installed. pbuilder-satisfydepends-dummy depends on dh-sequence-python3; however: Package dh-sequence-python3 is not installed. pbuilder-satisfydepends-dummy depends on git-core; however: Package git-core is not installed. pbuilder-satisfydepends-dummy depends on localehelper; however: Package localehelper is not installed. pbuilder-satisfydepends-dummy depends on python3-all; however: Package python3-all is not installed. pbuilder-satisfydepends-dummy depends on python3-ansi; however: Package python3-ansi is not installed. pbuilder-satisfydepends-dummy depends on python3-bottle; however: Package python3-bottle is not installed. pbuilder-satisfydepends-dummy depends on python3-colorlog; however: Package python3-colorlog is not installed. pbuilder-satisfydepends-dummy depends on python3-daemonize; however: Package python3-daemonize is not installed. pbuilder-satisfydepends-dummy depends on python3-flask; however: Package python3-flask is not installed. pbuilder-satisfydepends-dummy depends on python3-jinja2; however: Package python3-jinja2 is not installed. pbuilder-satisfydepends-dummy depends on python3-markdown; however: Package python3-markdown is not installed. pbuilder-satisfydepends-dummy depends on python3-openssl; however: Package python3-openssl is not installed. pbuilder-satisfydepends-dummy depends on python3-pygments; however: Package python3-pygments is not installed. pbuilder-satisfydepends-dummy depends on python3-pytest; however: Package python3-pytest is not installed. pbuilder-satisfydepends-dummy depends on python3-requests; however: Package python3-requests is not installed. pbuilder-satisfydepends-dummy depends on python3-setuptools; however: Package python3-setuptools is not installed. pbuilder-satisfydepends-dummy depends on python3-webtest; however: Package python3-webtest is not installed. pbuilder-satisfydepends-dummy depends on python3-yapsy; however: Package python3-yapsy is not installed. pbuilder-satisfydepends-dummy depends on python3-dulwich; however: Package python3-dulwich is not installed. Setting up pbuilder-satisfydepends-dummy (0.invalid.0) ... Reading package lists... Building dependency tree... Reading state information... Initializing package states... Writing extended state information... Building tag database... pbuilder-satisfydepends-dummy is already installed at the requested version (0.invalid.0) pbuilder-satisfydepends-dummy is already installed at the requested version (0.invalid.0) The following NEW packages will be installed: autoconf{a} automake{a} autopoint{a} autotools-dev{a} bsdextrautils{a} ca-certificates{a} debhelper{a} dh-autoreconf{a} dh-python{a} dh-strip-nondeterminism{a} dwz{a} file{a} gettext{a} gettext-base{a} git{a} git-man{a} groff-base{a} intltool-debian{a} libarchive-zip-perl{a} libbrotli1{a} libcom-err2{a} libcurl3t64-gnutls{a} libdebhelper-perl{a} libelf1t64{a} liberror-perl{a} libexpat1{a} libffi8{a} libfile-stripnondeterminism-perl{a} libgnutls30t64{a} libgssapi-krb5-2{a} libicu72{a} libidn2-0{a} libk5crypto3{a} libkeyutils1{a} libkrb5-3{a} libkrb5support0{a} libldap2{a} libmagic-mgc{a} libmagic1t64{a} libnghttp2-14{a} libnghttp3-9{a} libngtcp2-16{a} libngtcp2-crypto-gnutls8{a} libnsl2{a} libp11-kit0{a} libpipeline1{a} libpsl5t64{a} libpython3-stdlib{a} libpython3.12-minimal{a} libpython3.12-stdlib{a} libpython3.13-minimal{a} libpython3.13-stdlib{a} libreadline8t64{a} librtmp1{a} libsasl2-2{a} libsasl2-modules-db{a} libssh2-1t64{a} libtasn1-6{a} libtirpc-common{a} libtirpc3t64{a} libtool{a} libuchardet0{a} libunistring5{a} libxml2{a} localehelper{a} locales{a} m4{a} man-db{a} media-types{a} netbase{a} openssl{a} po-debconf{a} python3{a} python3-all{a} python3-ansi{a} python3-autocommand{a} python3-bcrypt{a} python3-blinker{a} python3-bottle{a} python3-bs4{a} python3-certifi{a} python3-cffi-backend{a} python3-chardet{a} python3-charset-normalizer{a} python3-click{a} python3-colorlog{a} python3-cryptography{a} python3-daemonize{a} python3-dulwich{a} python3-flask{a} python3-idna{a} python3-inflect{a} python3-iniconfig{a} python3-itsdangerous{a} python3-jaraco.context{a} python3-jaraco.functools{a} python3-jaraco.text{a} python3-jinja2{a} python3-legacy-cgi{a} python3-markdown{a} python3-markupsafe{a} python3-minimal{a} python3-more-itertools{a} python3-openssl{a} python3-packaging{a} python3-paste{a} python3-pastedeploy{a} python3-pastedeploy-tpl{a} python3-pkg-resources{a} python3-pluggy{a} python3-pygments{a} python3-pytest{a} python3-requests{a} python3-setuptools{a} python3-soupsieve{a} python3-tempita{a} python3-typeguard{a} python3-typing-extensions{a} python3-urllib3{a} python3-waitress{a} python3-webob{a} python3-webtest{a} python3-werkzeug{a} python3-yapsy{a} python3-zipp{a} python3.12{a} python3.12-minimal{a} python3.13{a} python3.13-minimal{a} readline-common{a} sensible-utils{a} tzdata{a} The following packages are RECOMMENDED but will NOT be installed: curl krb5-locales less libarchive-cpio-perl libldap-common libltdl-dev libmail-sendmail-perl libsasl2-modules lynx openssh-client publicsuffix python3-asgiref python3-babel python3-dotenv python3-fastimport python3-lxml python3-pastescript python3-pyinotify python3-yaml wget 0 packages upgraded, 132 newly installed, 0 to remove and 0 not upgraded. Need to get 60.6 MB of archives. After unpacking 246 MB will be used. Writing extended state information... Get: 1 http://deb.debian.org/debian trixie/main i386 libpython3.13-minimal i386 3.13.1-3+b1 [858 kB] Get: 2 http://deb.debian.org/debian trixie/main i386 libexpat1 i386 2.6.4-1 [107 kB] Get: 3 http://deb.debian.org/debian trixie/main i386 python3.13-minimal i386 3.13.1-3+b1 [2244 kB] Get: 4 http://deb.debian.org/debian trixie/main i386 python3-minimal i386 3.13.1-2 [27.0 kB] Get: 5 http://deb.debian.org/debian trixie/main i386 media-types all 10.1.0 [26.9 kB] Get: 6 http://deb.debian.org/debian trixie/main i386 netbase all 6.4 [12.8 kB] Get: 7 http://deb.debian.org/debian trixie/main i386 tzdata all 2024b-6 [257 kB] Get: 8 http://deb.debian.org/debian trixie/main i386 libffi8 i386 3.4.6-1 [21.2 kB] Get: 9 http://deb.debian.org/debian trixie/main i386 readline-common all 8.2-6 [69.4 kB] Get: 10 http://deb.debian.org/debian trixie/main i386 libreadline8t64 i386 8.2-6 [173 kB] Get: 11 http://deb.debian.org/debian trixie/main i386 libpython3.13-stdlib i386 3.13.1-3+b1 [1981 kB] Get: 12 http://deb.debian.org/debian trixie/main i386 python3.13 i386 3.13.1-3+b1 [740 kB] Get: 13 http://deb.debian.org/debian trixie/main i386 libpython3-stdlib i386 3.13.1-2 [9952 B] Get: 14 http://deb.debian.org/debian trixie/main i386 python3 i386 3.13.1-2 [28.0 kB] Get: 15 http://deb.debian.org/debian trixie/main i386 libpython3.12-minimal i386 3.12.8-5+b1 [816 kB] Get: 16 http://deb.debian.org/debian trixie/main i386 python3.12-minimal i386 3.12.8-5+b1 [2239 kB] Get: 17 http://deb.debian.org/debian trixie/main i386 sensible-utils all 0.0.24 [24.8 kB] Get: 18 http://deb.debian.org/debian trixie/main i386 openssl i386 3.4.0-2 [1427 kB] Get: 19 http://deb.debian.org/debian trixie/main i386 ca-certificates all 20241223 [164 kB] Get: 20 http://deb.debian.org/debian trixie/main i386 libmagic-mgc i386 1:5.45-3+b1 [314 kB] Get: 21 http://deb.debian.org/debian trixie/main i386 libmagic1t64 i386 1:5.45-3+b1 [115 kB] Get: 22 http://deb.debian.org/debian trixie/main i386 file i386 1:5.45-3+b1 [43.2 kB] Get: 23 http://deb.debian.org/debian trixie/main i386 gettext-base i386 0.23.1-1 [245 kB] Get: 24 http://deb.debian.org/debian trixie/main i386 libuchardet0 i386 0.0.8-1+b2 [69.2 kB] Get: 25 http://deb.debian.org/debian trixie/main i386 groff-base i386 1.23.0-7 [1199 kB] Get: 26 http://deb.debian.org/debian trixie/main i386 locales all 2.40-6 [3901 kB] Get: 27 http://deb.debian.org/debian trixie/main i386 bsdextrautils i386 2.40.4-2 [96.1 kB] Get: 28 http://deb.debian.org/debian trixie/main i386 libpipeline1 i386 1.5.8-1 [41.2 kB] Get: 29 http://deb.debian.org/debian trixie/main i386 man-db i386 2.13.0-1 [1428 kB] Get: 30 http://deb.debian.org/debian trixie/main i386 m4 i386 1.4.19-5 [301 kB] Get: 31 http://deb.debian.org/debian trixie/main i386 autoconf all 2.72-3 [493 kB] Get: 32 http://deb.debian.org/debian trixie/main i386 autotools-dev all 20220109.1 [51.6 kB] Get: 33 http://deb.debian.org/debian trixie/main i386 automake all 1:1.16.5-1.3 [823 kB] Get: 34 http://deb.debian.org/debian trixie/main i386 autopoint all 0.23.1-1 [770 kB] Get: 35 http://deb.debian.org/debian trixie/main i386 libdebhelper-perl all 13.24.1 [90.9 kB] Get: 36 http://deb.debian.org/debian trixie/main i386 libtool all 2.5.4-2 [539 kB] Get: 37 http://deb.debian.org/debian trixie/main i386 dh-autoreconf all 20 [17.1 kB] Get: 38 http://deb.debian.org/debian trixie/main i386 libarchive-zip-perl all 1.68-1 [104 kB] Get: 39 http://deb.debian.org/debian trixie/main i386 libfile-stripnondeterminism-perl all 1.14.1-1 [19.6 kB] Get: 40 http://deb.debian.org/debian trixie/main i386 dh-strip-nondeterminism all 1.14.1-1 [8584 B] Get: 41 http://deb.debian.org/debian trixie/main i386 libelf1t64 i386 0.192-4 [195 kB] Get: 42 http://deb.debian.org/debian trixie/main i386 dwz i386 0.15-1+b1 [116 kB] Get: 43 http://deb.debian.org/debian trixie/main i386 libunistring5 i386 1.3-1 [458 kB] Get: 44 http://deb.debian.org/debian trixie/main i386 libicu72 i386 72.1-6 [9582 kB] Get: 45 http://deb.debian.org/debian trixie/main i386 libxml2 i386 2.12.7+dfsg+really2.9.14-0.2+b1 [734 kB] Get: 46 http://deb.debian.org/debian trixie/main i386 gettext i386 0.23.1-1 [1714 kB] Get: 47 http://deb.debian.org/debian trixie/main i386 intltool-debian all 0.35.0+20060710.6 [22.9 kB] Get: 48 http://deb.debian.org/debian trixie/main i386 po-debconf all 1.0.21+nmu1 [248 kB] Get: 49 http://deb.debian.org/debian trixie/main i386 debhelper all 13.24.1 [920 kB] Get: 50 http://deb.debian.org/debian trixie/main i386 python3-autocommand all 2.2.2-3 [13.6 kB] Get: 51 http://deb.debian.org/debian trixie/main i386 python3-more-itertools all 10.6.0-1 [65.3 kB] Get: 52 http://deb.debian.org/debian trixie/main i386 python3-typing-extensions all 4.12.2-2 [73.0 kB] Get: 53 http://deb.debian.org/debian trixie/main i386 python3-typeguard all 4.4.1-1 [37.0 kB] Get: 54 http://deb.debian.org/debian trixie/main i386 python3-inflect all 7.3.1-2 [32.4 kB] Get: 55 http://deb.debian.org/debian trixie/main i386 python3-jaraco.context all 6.0.0-1 [7984 B] Get: 56 http://deb.debian.org/debian trixie/main i386 python3-jaraco.functools all 4.1.0-1 [12.0 kB] Get: 57 http://deb.debian.org/debian trixie/main i386 python3-pkg-resources all 75.6.0-1 [222 kB] Get: 58 http://deb.debian.org/debian trixie/main i386 python3-jaraco.text all 4.0.0-1 [11.4 kB] Get: 59 http://deb.debian.org/debian trixie/main i386 python3-zipp all 3.21.0-1 [10.6 kB] Get: 60 http://deb.debian.org/debian trixie/main i386 python3-setuptools all 75.6.0-1 [720 kB] Get: 61 http://deb.debian.org/debian trixie/main i386 dh-python all 6.20250108 [113 kB] Get: 62 http://deb.debian.org/debian trixie/main i386 libbrotli1 i386 1.1.0-2+b6 [308 kB] Get: 63 http://deb.debian.org/debian trixie/main i386 libidn2-0 i386 2.3.7-2+b1 [130 kB] Get: 64 http://deb.debian.org/debian trixie/main i386 libp11-kit0 i386 0.25.5-3 [423 kB] Get: 65 http://deb.debian.org/debian trixie/main i386 libtasn1-6 i386 4.19.0-3+b3 [51.1 kB] Get: 66 http://deb.debian.org/debian trixie/main i386 libgnutls30t64 i386 3.8.8-2 [1451 kB] Get: 67 http://deb.debian.org/debian trixie/main i386 libkrb5support0 i386 1.21.3-4 [35.0 kB] Get: 68 http://deb.debian.org/debian trixie/main i386 libcom-err2 i386 1.47.2-1 [24.3 kB] Get: 69 http://deb.debian.org/debian trixie/main i386 libk5crypto3 i386 1.21.3-4 [83.7 kB] Get: 70 http://deb.debian.org/debian trixie/main i386 libkeyutils1 i386 1.6.3-4 [9600 B] Get: 71 http://deb.debian.org/debian trixie/main i386 libkrb5-3 i386 1.21.3-4 [354 kB] Get: 72 http://deb.debian.org/debian trixie/main i386 libgssapi-krb5-2 i386 1.21.3-4 [149 kB] Get: 73 http://deb.debian.org/debian trixie/main i386 libsasl2-modules-db i386 2.1.28+dfsg1-8+b1 [20.9 kB] Get: 74 http://deb.debian.org/debian trixie/main i386 libsasl2-2 i386 2.1.28+dfsg1-8+b1 [61.3 kB] Get: 75 http://deb.debian.org/debian trixie/main i386 libldap2 i386 2.6.9+dfsg-1 [205 kB] Get: 76 http://deb.debian.org/debian trixie/main i386 libnghttp2-14 i386 1.64.0-1 [82.4 kB] Get: 77 http://deb.debian.org/debian trixie/main i386 libnghttp3-9 i386 1.6.0-2 [75.9 kB] Get: 78 http://deb.debian.org/debian trixie/main i386 libngtcp2-16 i386 1.9.1-1 [151 kB] Get: 79 http://deb.debian.org/debian trixie/main i386 libngtcp2-crypto-gnutls8 i386 1.9.1-1 [19.1 kB] Get: 80 http://deb.debian.org/debian trixie/main i386 libpsl5t64 i386 0.21.2-1.1+b1 [57.7 kB] Get: 81 http://deb.debian.org/debian trixie/main i386 librtmp1 i386 2.4+20151223.gitfa8646d.1-2+b5 [62.4 kB] Get: 82 http://deb.debian.org/debian trixie/main i386 libssh2-1t64 i386 1.11.1-1 [256 kB] Get: 83 http://deb.debian.org/debian trixie/main i386 libcurl3t64-gnutls i386 8.11.1-1+b1 [404 kB] Get: 84 http://deb.debian.org/debian trixie/main i386 liberror-perl all 0.17029-2 [29.0 kB] Get: 85 http://deb.debian.org/debian trixie/main i386 git-man all 1:2.47.2-0.1 [2205 kB] Get: 86 http://deb.debian.org/debian trixie/main i386 git i386 1:2.47.2-0.1 [9337 kB] Get: 87 http://deb.debian.org/debian trixie/main i386 libtirpc-common all 1.3.4+ds-1.3 [10.9 kB] Get: 88 http://deb.debian.org/debian trixie/main i386 libtirpc3t64 i386 1.3.4+ds-1.3+b1 [90.5 kB] Get: 89 http://deb.debian.org/debian trixie/main i386 libnsl2 i386 1.3.0-3+b3 [42.7 kB] Get: 90 http://deb.debian.org/debian trixie/main i386 libpython3.12-stdlib i386 3.12.8-5+b1 [1967 kB] Get: 91 http://deb.debian.org/debian trixie/main i386 localehelper all 0.1.4-3.1 [7928 B] Get: 92 http://deb.debian.org/debian trixie/main i386 python3.12 i386 3.12.8-5+b1 [678 kB] Get: 93 http://deb.debian.org/debian trixie/main i386 python3-all i386 3.13.1-2 [1052 B] Get: 94 http://deb.debian.org/debian trixie/main i386 python3-ansi all 0.1.5-2 [6232 B] Get: 95 http://deb.debian.org/debian trixie/main i386 python3-bcrypt i386 4.2.0-2.1 [248 kB] Get: 96 http://deb.debian.org/debian trixie/main i386 python3-blinker all 1.9.0-1 [12.6 kB] Get: 97 http://deb.debian.org/debian trixie/main i386 python3-bottle all 0.13.2-1.1 [54.3 kB] Get: 98 http://deb.debian.org/debian trixie/main i386 python3-soupsieve all 2.6-1 [38.3 kB] Get: 99 http://deb.debian.org/debian trixie/main i386 python3-bs4 all 4.12.3-3 [133 kB] Get: 100 http://deb.debian.org/debian trixie/main i386 python3-certifi all 2024.12.14+ds-1 [9624 B] Get: 101 http://deb.debian.org/debian trixie/main i386 python3-cffi-backend i386 1.17.1-2+b1 [95.5 kB] Get: 102 http://deb.debian.org/debian trixie/main i386 python3-chardet all 5.2.0+dfsg-2 [108 kB] Get: 103 http://deb.debian.org/debian trixie/main i386 python3-charset-normalizer i386 3.4.0-1+b1 [139 kB] Get: 104 http://deb.debian.org/debian trixie/main i386 python3-click all 8.1.8-1 [95.2 kB] Get: 105 http://deb.debian.org/debian trixie/main i386 python3-colorlog all 6.9.0-1 [27.5 kB] Get: 106 http://deb.debian.org/debian trixie/main i386 python3-cryptography i386 43.0.0-1 [975 kB] Get: 107 http://deb.debian.org/debian trixie/main i386 python3-daemonize all 2.5.0-3 [6324 B] Get: 108 http://deb.debian.org/debian trixie/main i386 python3-urllib3 all 2.3.0-1 [114 kB] Get: 109 http://deb.debian.org/debian trixie/main i386 python3-dulwich i386 0.22.7-1 [205 kB] Get: 110 http://deb.debian.org/debian trixie/main i386 python3-itsdangerous all 2.2.0-2 [18.1 kB] Get: 111 http://deb.debian.org/debian trixie/main i386 python3-markupsafe i386 2.1.5-1+b2 [13.9 kB] Get: 112 http://deb.debian.org/debian trixie/main i386 python3-jinja2 all 3.1.5-1 [107 kB] Get: 113 http://deb.debian.org/debian trixie/main i386 python3-werkzeug all 3.1.3-2 [207 kB] Get: 114 http://deb.debian.org/debian trixie/main i386 python3-flask all 3.1.0-2 [106 kB] Get: 115 http://deb.debian.org/debian trixie/main i386 python3-idna all 3.8-2 [41.6 kB] Get: 116 http://deb.debian.org/debian trixie/main i386 python3-iniconfig all 1.1.1-2 [6396 B] Get: 117 http://deb.debian.org/debian trixie/main i386 python3-legacy-cgi all 2.6.1-2 [16.1 kB] Get: 118 http://deb.debian.org/debian trixie/main i386 python3-markdown all 3.7-2 [85.2 kB] Get: 119 http://deb.debian.org/debian trixie/main i386 python3-openssl all 25.0.0-1 [52.1 kB] Get: 120 http://deb.debian.org/debian trixie/main i386 python3-packaging all 24.2-1 [55.3 kB] Get: 121 http://deb.debian.org/debian trixie/main i386 python3-tempita all 0.6.0-1 [14.6 kB] Get: 122 http://deb.debian.org/debian trixie/main i386 python3-paste all 3.10.1-1 [222 kB] Get: 123 http://deb.debian.org/debian trixie/main i386 python3-pastedeploy-tpl all 3.1-1 [8268 B] Get: 124 http://deb.debian.org/debian trixie/main i386 python3-pastedeploy all 3.1-1 [18.3 kB] Get: 125 http://deb.debian.org/debian trixie/main i386 python3-pluggy all 1.5.0-1 [26.9 kB] Get: 126 http://deb.debian.org/debian trixie/main i386 python3-pygments all 2.18.0+dfsg-2 [836 kB] Get: 127 http://deb.debian.org/debian trixie/main i386 python3-pytest all 8.3.4-1 [250 kB] Get: 128 http://deb.debian.org/debian trixie/main i386 python3-requests all 2.32.3+dfsg-1 [71.9 kB] Get: 129 http://deb.debian.org/debian trixie/main i386 python3-waitress all 3.0.2-1 [46.5 kB] Get: 130 http://deb.debian.org/debian trixie/main i386 python3-webob all 1:1.8.9-1 [89.1 kB] Get: 131 http://deb.debian.org/debian trixie/main i386 python3-webtest all 3.0.2-1 [35.2 kB] Get: 132 http://deb.debian.org/debian trixie/main i386 python3-yapsy all 1.12.2-3 [30.4 kB] Fetched 60.6 MB in 1s (76.6 MB/s) Preconfiguring packages ... Selecting previously unselected package libpython3.13-minimal:i386. (Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 19834 files and directories currently installed.) Preparing to unpack .../libpython3.13-minimal_3.13.1-3+b1_i386.deb ... Unpacking libpython3.13-minimal:i386 (3.13.1-3+b1) ... Selecting previously unselected package libexpat1:i386. Preparing to unpack .../libexpat1_2.6.4-1_i386.deb ... Unpacking libexpat1:i386 (2.6.4-1) ... Selecting previously unselected package python3.13-minimal. Preparing to unpack .../python3.13-minimal_3.13.1-3+b1_i386.deb ... Unpacking python3.13-minimal (3.13.1-3+b1) ... Setting up libpython3.13-minimal:i386 (3.13.1-3+b1) ... Setting up libexpat1:i386 (2.6.4-1) ... Setting up python3.13-minimal (3.13.1-3+b1) ... Selecting previously unselected package python3-minimal. (Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 20170 files and directories currently installed.) Preparing to unpack .../0-python3-minimal_3.13.1-2_i386.deb ... Unpacking python3-minimal (3.13.1-2) ... Selecting previously unselected package media-types. Preparing to unpack .../1-media-types_10.1.0_all.deb ... Unpacking media-types (10.1.0) ... Selecting previously unselected package netbase. Preparing to unpack .../2-netbase_6.4_all.deb ... Unpacking netbase (6.4) ... Selecting previously unselected package tzdata. Preparing to unpack .../3-tzdata_2024b-6_all.deb ... Unpacking tzdata (2024b-6) ... Selecting previously unselected package libffi8:i386. Preparing to unpack .../4-libffi8_3.4.6-1_i386.deb ... Unpacking libffi8:i386 (3.4.6-1) ... Selecting previously unselected package readline-common. Preparing to unpack .../5-readline-common_8.2-6_all.deb ... Unpacking readline-common (8.2-6) ... Selecting previously unselected package libreadline8t64:i386. Preparing to unpack .../6-libreadline8t64_8.2-6_i386.deb ... Adding 'diversion of /lib/i386-linux-gnu/libhistory.so.8 to /lib/i386-linux-gnu/libhistory.so.8.usr-is-merged by libreadline8t64' Adding 'diversion of /lib/i386-linux-gnu/libhistory.so.8.2 to /lib/i386-linux-gnu/libhistory.so.8.2.usr-is-merged by libreadline8t64' Adding 'diversion of /lib/i386-linux-gnu/libreadline.so.8 to /lib/i386-linux-gnu/libreadline.so.8.usr-is-merged by libreadline8t64' Adding 'diversion of /lib/i386-linux-gnu/libreadline.so.8.2 to /lib/i386-linux-gnu/libreadline.so.8.2.usr-is-merged by libreadline8t64' Unpacking libreadline8t64:i386 (8.2-6) ... Selecting previously unselected package libpython3.13-stdlib:i386. Preparing to unpack .../7-libpython3.13-stdlib_3.13.1-3+b1_i386.deb ... Unpacking libpython3.13-stdlib:i386 (3.13.1-3+b1) ... Selecting previously unselected package python3.13. Preparing to unpack .../8-python3.13_3.13.1-3+b1_i386.deb ... Unpacking python3.13 (3.13.1-3+b1) ... Selecting previously unselected package libpython3-stdlib:i386. Preparing to unpack .../9-libpython3-stdlib_3.13.1-2_i386.deb ... Unpacking libpython3-stdlib:i386 (3.13.1-2) ... Setting up python3-minimal (3.13.1-2) ... Selecting previously unselected package python3. (Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 21181 files and directories currently installed.) Preparing to unpack .../000-python3_3.13.1-2_i386.deb ... Unpacking python3 (3.13.1-2) ... Selecting previously unselected package libpython3.12-minimal:i386. Preparing to unpack .../001-libpython3.12-minimal_3.12.8-5+b1_i386.deb ... Unpacking libpython3.12-minimal:i386 (3.12.8-5+b1) ... Selecting previously unselected package python3.12-minimal. Preparing to unpack .../002-python3.12-minimal_3.12.8-5+b1_i386.deb ... Unpacking python3.12-minimal (3.12.8-5+b1) ... Selecting previously unselected package sensible-utils. Preparing to unpack .../003-sensible-utils_0.0.24_all.deb ... Unpacking sensible-utils (0.0.24) ... Selecting previously unselected package openssl. Preparing to unpack .../004-openssl_3.4.0-2_i386.deb ... Unpacking openssl (3.4.0-2) ... Selecting previously unselected package ca-certificates. Preparing to unpack .../005-ca-certificates_20241223_all.deb ... Unpacking ca-certificates (20241223) ... Selecting previously unselected package libmagic-mgc. Preparing to unpack .../006-libmagic-mgc_1%3a5.45-3+b1_i386.deb ... Unpacking libmagic-mgc (1:5.45-3+b1) ... Selecting previously unselected package libmagic1t64:i386. Preparing to unpack .../007-libmagic1t64_1%3a5.45-3+b1_i386.deb ... Unpacking libmagic1t64:i386 (1:5.45-3+b1) ... Selecting previously unselected package file. Preparing to unpack .../008-file_1%3a5.45-3+b1_i386.deb ... Unpacking file (1:5.45-3+b1) ... Selecting previously unselected package gettext-base. Preparing to unpack .../009-gettext-base_0.23.1-1_i386.deb ... Unpacking gettext-base (0.23.1-1) ... Selecting previously unselected package libuchardet0:i386. Preparing to unpack .../010-libuchardet0_0.0.8-1+b2_i386.deb ... Unpacking libuchardet0:i386 (0.0.8-1+b2) ... Selecting previously unselected package groff-base. Preparing to unpack .../011-groff-base_1.23.0-7_i386.deb ... Unpacking groff-base (1.23.0-7) ... Selecting previously unselected package locales. Preparing to unpack .../012-locales_2.40-6_all.deb ... Unpacking locales (2.40-6) ... Selecting previously unselected package bsdextrautils. Preparing to unpack .../013-bsdextrautils_2.40.4-2_i386.deb ... Unpacking bsdextrautils (2.40.4-2) ... Selecting previously unselected package libpipeline1:i386. Preparing to unpack .../014-libpipeline1_1.5.8-1_i386.deb ... Unpacking libpipeline1:i386 (1.5.8-1) ... Selecting previously unselected package man-db. Preparing to unpack .../015-man-db_2.13.0-1_i386.deb ... Unpacking man-db (2.13.0-1) ... Selecting previously unselected package m4. Preparing to unpack .../016-m4_1.4.19-5_i386.deb ... Unpacking m4 (1.4.19-5) ... Selecting previously unselected package autoconf. Preparing to unpack .../017-autoconf_2.72-3_all.deb ... Unpacking autoconf (2.72-3) ... Selecting previously unselected package autotools-dev. Preparing to unpack .../018-autotools-dev_20220109.1_all.deb ... Unpacking autotools-dev (20220109.1) ... Selecting previously unselected package automake. Preparing to unpack .../019-automake_1%3a1.16.5-1.3_all.deb ... Unpacking automake (1:1.16.5-1.3) ... Selecting previously unselected package autopoint. Preparing to unpack .../020-autopoint_0.23.1-1_all.deb ... Unpacking autopoint (0.23.1-1) ... Selecting previously unselected package libdebhelper-perl. Preparing to unpack .../021-libdebhelper-perl_13.24.1_all.deb ... Unpacking libdebhelper-perl (13.24.1) ... Selecting previously unselected package libtool. Preparing to unpack .../022-libtool_2.5.4-2_all.deb ... Unpacking libtool (2.5.4-2) ... Selecting previously unselected package dh-autoreconf. Preparing to unpack .../023-dh-autoreconf_20_all.deb ... Unpacking dh-autoreconf (20) ... Selecting previously unselected package libarchive-zip-perl. Preparing to unpack .../024-libarchive-zip-perl_1.68-1_all.deb ... Unpacking libarchive-zip-perl (1.68-1) ... Selecting previously unselected package libfile-stripnondeterminism-perl. Preparing to unpack .../025-libfile-stripnondeterminism-perl_1.14.1-1_all.deb ... Unpacking libfile-stripnondeterminism-perl (1.14.1-1) ... Selecting previously unselected package dh-strip-nondeterminism. Preparing to unpack .../026-dh-strip-nondeterminism_1.14.1-1_all.deb ... Unpacking dh-strip-nondeterminism (1.14.1-1) ... Selecting previously unselected package libelf1t64:i386. Preparing to unpack .../027-libelf1t64_0.192-4_i386.deb ... Unpacking libelf1t64:i386 (0.192-4) ... Selecting previously unselected package dwz. Preparing to unpack .../028-dwz_0.15-1+b1_i386.deb ... Unpacking dwz (0.15-1+b1) ... Selecting previously unselected package libunistring5:i386. Preparing to unpack .../029-libunistring5_1.3-1_i386.deb ... Unpacking libunistring5:i386 (1.3-1) ... Selecting previously unselected package libicu72:i386. Preparing to unpack .../030-libicu72_72.1-6_i386.deb ... Unpacking libicu72:i386 (72.1-6) ... Selecting previously unselected package libxml2:i386. Preparing to unpack .../031-libxml2_2.12.7+dfsg+really2.9.14-0.2+b1_i386.deb ... Unpacking libxml2:i386 (2.12.7+dfsg+really2.9.14-0.2+b1) ... Selecting previously unselected package gettext. Preparing to unpack .../032-gettext_0.23.1-1_i386.deb ... Unpacking gettext (0.23.1-1) ... Selecting previously unselected package intltool-debian. Preparing to unpack .../033-intltool-debian_0.35.0+20060710.6_all.deb ... Unpacking intltool-debian (0.35.0+20060710.6) ... Selecting previously unselected package po-debconf. Preparing to unpack .../034-po-debconf_1.0.21+nmu1_all.deb ... Unpacking po-debconf (1.0.21+nmu1) ... Selecting previously unselected package debhelper. Preparing to unpack .../035-debhelper_13.24.1_all.deb ... Unpacking debhelper (13.24.1) ... Selecting previously unselected package python3-autocommand. Preparing to unpack .../036-python3-autocommand_2.2.2-3_all.deb ... Unpacking python3-autocommand (2.2.2-3) ... Selecting previously unselected package python3-more-itertools. Preparing to unpack .../037-python3-more-itertools_10.6.0-1_all.deb ... Unpacking python3-more-itertools (10.6.0-1) ... Selecting previously unselected package python3-typing-extensions. Preparing to unpack .../038-python3-typing-extensions_4.12.2-2_all.deb ... Unpacking python3-typing-extensions (4.12.2-2) ... Selecting previously unselected package python3-typeguard. Preparing to unpack .../039-python3-typeguard_4.4.1-1_all.deb ... Unpacking python3-typeguard (4.4.1-1) ... Selecting previously unselected package python3-inflect. Preparing to unpack .../040-python3-inflect_7.3.1-2_all.deb ... Unpacking python3-inflect (7.3.1-2) ... Selecting previously unselected package python3-jaraco.context. Preparing to unpack .../041-python3-jaraco.context_6.0.0-1_all.deb ... Unpacking python3-jaraco.context (6.0.0-1) ... Selecting previously unselected package python3-jaraco.functools. Preparing to unpack .../042-python3-jaraco.functools_4.1.0-1_all.deb ... Unpacking python3-jaraco.functools (4.1.0-1) ... Selecting previously unselected package python3-pkg-resources. Preparing to unpack .../043-python3-pkg-resources_75.6.0-1_all.deb ... Unpacking python3-pkg-resources (75.6.0-1) ... Selecting previously unselected package python3-jaraco.text. Preparing to unpack .../044-python3-jaraco.text_4.0.0-1_all.deb ... Unpacking python3-jaraco.text (4.0.0-1) ... Selecting previously unselected package python3-zipp. Preparing to unpack .../045-python3-zipp_3.21.0-1_all.deb ... Unpacking python3-zipp (3.21.0-1) ... Selecting previously unselected package python3-setuptools. Preparing to unpack .../046-python3-setuptools_75.6.0-1_all.deb ... Unpacking python3-setuptools (75.6.0-1) ... Selecting previously unselected package dh-python. Preparing to unpack .../047-dh-python_6.20250108_all.deb ... Unpacking dh-python (6.20250108) ... Selecting previously unselected package libbrotli1:i386. Preparing to unpack .../048-libbrotli1_1.1.0-2+b6_i386.deb ... Unpacking libbrotli1:i386 (1.1.0-2+b6) ... Selecting previously unselected package libidn2-0:i386. Preparing to unpack .../049-libidn2-0_2.3.7-2+b1_i386.deb ... Unpacking libidn2-0:i386 (2.3.7-2+b1) ... Selecting previously unselected package libp11-kit0:i386. Preparing to unpack .../050-libp11-kit0_0.25.5-3_i386.deb ... Unpacking libp11-kit0:i386 (0.25.5-3) ... Selecting previously unselected package libtasn1-6:i386. Preparing to unpack .../051-libtasn1-6_4.19.0-3+b3_i386.deb ... Unpacking libtasn1-6:i386 (4.19.0-3+b3) ... Selecting previously unselected package libgnutls30t64:i386. Preparing to unpack .../052-libgnutls30t64_3.8.8-2_i386.deb ... Unpacking libgnutls30t64:i386 (3.8.8-2) ... Selecting previously unselected package libkrb5support0:i386. Preparing to unpack .../053-libkrb5support0_1.21.3-4_i386.deb ... Unpacking libkrb5support0:i386 (1.21.3-4) ... Selecting previously unselected package libcom-err2:i386. Preparing to unpack .../054-libcom-err2_1.47.2-1_i386.deb ... Unpacking libcom-err2:i386 (1.47.2-1) ... Selecting previously unselected package libk5crypto3:i386. Preparing to unpack .../055-libk5crypto3_1.21.3-4_i386.deb ... Unpacking libk5crypto3:i386 (1.21.3-4) ... Selecting previously unselected package libkeyutils1:i386. Preparing to unpack .../056-libkeyutils1_1.6.3-4_i386.deb ... Unpacking libkeyutils1:i386 (1.6.3-4) ... Selecting previously unselected package libkrb5-3:i386. Preparing to unpack .../057-libkrb5-3_1.21.3-4_i386.deb ... Unpacking libkrb5-3:i386 (1.21.3-4) ... Selecting previously unselected package libgssapi-krb5-2:i386. Preparing to unpack .../058-libgssapi-krb5-2_1.21.3-4_i386.deb ... Unpacking libgssapi-krb5-2:i386 (1.21.3-4) ... Selecting previously unselected package libsasl2-modules-db:i386. Preparing to unpack .../059-libsasl2-modules-db_2.1.28+dfsg1-8+b1_i386.deb ... Unpacking libsasl2-modules-db:i386 (2.1.28+dfsg1-8+b1) ... Selecting previously unselected package libsasl2-2:i386. Preparing to unpack .../060-libsasl2-2_2.1.28+dfsg1-8+b1_i386.deb ... Unpacking libsasl2-2:i386 (2.1.28+dfsg1-8+b1) ... Selecting previously unselected package libldap2:i386. Preparing to unpack .../061-libldap2_2.6.9+dfsg-1_i386.deb ... Unpacking libldap2:i386 (2.6.9+dfsg-1) ... Selecting previously unselected package libnghttp2-14:i386. Preparing to unpack .../062-libnghttp2-14_1.64.0-1_i386.deb ... Unpacking libnghttp2-14:i386 (1.64.0-1) ... Selecting previously unselected package libnghttp3-9:i386. Preparing to unpack .../063-libnghttp3-9_1.6.0-2_i386.deb ... Unpacking libnghttp3-9:i386 (1.6.0-2) ... Selecting previously unselected package libngtcp2-16:i386. Preparing to unpack .../064-libngtcp2-16_1.9.1-1_i386.deb ... Unpacking libngtcp2-16:i386 (1.9.1-1) ... Selecting previously unselected package libngtcp2-crypto-gnutls8:i386. Preparing to unpack .../065-libngtcp2-crypto-gnutls8_1.9.1-1_i386.deb ... Unpacking libngtcp2-crypto-gnutls8:i386 (1.9.1-1) ... Selecting previously unselected package libpsl5t64:i386. Preparing to unpack .../066-libpsl5t64_0.21.2-1.1+b1_i386.deb ... Unpacking libpsl5t64:i386 (0.21.2-1.1+b1) ... Selecting previously unselected package librtmp1:i386. Preparing to unpack .../067-librtmp1_2.4+20151223.gitfa8646d.1-2+b5_i386.deb ... Unpacking librtmp1:i386 (2.4+20151223.gitfa8646d.1-2+b5) ... Selecting previously unselected package libssh2-1t64:i386. Preparing to unpack .../068-libssh2-1t64_1.11.1-1_i386.deb ... Unpacking libssh2-1t64:i386 (1.11.1-1) ... Selecting previously unselected package libcurl3t64-gnutls:i386. Preparing to unpack .../069-libcurl3t64-gnutls_8.11.1-1+b1_i386.deb ... Unpacking libcurl3t64-gnutls:i386 (8.11.1-1+b1) ... Selecting previously unselected package liberror-perl. Preparing to unpack .../070-liberror-perl_0.17029-2_all.deb ... Unpacking liberror-perl (0.17029-2) ... Selecting previously unselected package git-man. Preparing to unpack .../071-git-man_1%3a2.47.2-0.1_all.deb ... Unpacking git-man (1:2.47.2-0.1) ... Selecting previously unselected package git. Preparing to unpack .../072-git_1%3a2.47.2-0.1_i386.deb ... Unpacking git (1:2.47.2-0.1) ... Selecting previously unselected package libtirpc-common. Preparing to unpack .../073-libtirpc-common_1.3.4+ds-1.3_all.deb ... Unpacking libtirpc-common (1.3.4+ds-1.3) ... Selecting previously unselected package libtirpc3t64:i386. Preparing to unpack .../074-libtirpc3t64_1.3.4+ds-1.3+b1_i386.deb ... Adding 'diversion of /lib/i386-linux-gnu/libtirpc.so.3 to /lib/i386-linux-gnu/libtirpc.so.3.usr-is-merged by libtirpc3t64' Adding 'diversion of /lib/i386-linux-gnu/libtirpc.so.3.0.0 to /lib/i386-linux-gnu/libtirpc.so.3.0.0.usr-is-merged by libtirpc3t64' Unpacking libtirpc3t64:i386 (1.3.4+ds-1.3+b1) ... Selecting previously unselected package libnsl2:i386. Preparing to unpack .../075-libnsl2_1.3.0-3+b3_i386.deb ... Unpacking libnsl2:i386 (1.3.0-3+b3) ... Selecting previously unselected package libpython3.12-stdlib:i386. Preparing to unpack .../076-libpython3.12-stdlib_3.12.8-5+b1_i386.deb ... Unpacking libpython3.12-stdlib:i386 (3.12.8-5+b1) ... Selecting previously unselected package localehelper. Preparing to unpack .../077-localehelper_0.1.4-3.1_all.deb ... Unpacking localehelper (0.1.4-3.1) ... Selecting previously unselected package python3.12. Preparing to unpack .../078-python3.12_3.12.8-5+b1_i386.deb ... Unpacking python3.12 (3.12.8-5+b1) ... Selecting previously unselected package python3-all. Preparing to unpack .../079-python3-all_3.13.1-2_i386.deb ... Unpacking python3-all (3.13.1-2) ... Selecting previously unselected package python3-ansi. Preparing to unpack .../080-python3-ansi_0.1.5-2_all.deb ... Unpacking python3-ansi (0.1.5-2) ... Selecting previously unselected package python3-bcrypt. Preparing to unpack .../081-python3-bcrypt_4.2.0-2.1_i386.deb ... Unpacking python3-bcrypt (4.2.0-2.1) ... Selecting previously unselected package python3-blinker. Preparing to unpack .../082-python3-blinker_1.9.0-1_all.deb ... Unpacking python3-blinker (1.9.0-1) ... Selecting previously unselected package python3-bottle. Preparing to unpack .../083-python3-bottle_0.13.2-1.1_all.deb ... Unpacking python3-bottle (0.13.2-1.1) ... Selecting previously unselected package python3-soupsieve. Preparing to unpack .../084-python3-soupsieve_2.6-1_all.deb ... Unpacking python3-soupsieve (2.6-1) ... Selecting previously unselected package python3-bs4. Preparing to unpack .../085-python3-bs4_4.12.3-3_all.deb ... Unpacking python3-bs4 (4.12.3-3) ... Selecting previously unselected package python3-certifi. Preparing to unpack .../086-python3-certifi_2024.12.14+ds-1_all.deb ... Unpacking python3-certifi (2024.12.14+ds-1) ... Selecting previously unselected package python3-cffi-backend:i386. Preparing to unpack .../087-python3-cffi-backend_1.17.1-2+b1_i386.deb ... Unpacking python3-cffi-backend:i386 (1.17.1-2+b1) ... Selecting previously unselected package python3-chardet. Preparing to unpack .../088-python3-chardet_5.2.0+dfsg-2_all.deb ... Unpacking python3-chardet (5.2.0+dfsg-2) ... Selecting previously unselected package python3-charset-normalizer. Preparing to unpack .../089-python3-charset-normalizer_3.4.0-1+b1_i386.deb ... Unpacking python3-charset-normalizer (3.4.0-1+b1) ... Selecting previously unselected package python3-click. Preparing to unpack .../090-python3-click_8.1.8-1_all.deb ... Unpacking python3-click (8.1.8-1) ... Selecting previously unselected package python3-colorlog. Preparing to unpack .../091-python3-colorlog_6.9.0-1_all.deb ... Unpacking python3-colorlog (6.9.0-1) ... Selecting previously unselected package python3-cryptography. Preparing to unpack .../092-python3-cryptography_43.0.0-1_i386.deb ... Unpacking python3-cryptography (43.0.0-1) ... Selecting previously unselected package python3-daemonize. Preparing to unpack .../093-python3-daemonize_2.5.0-3_all.deb ... Unpacking python3-daemonize (2.5.0-3) ... Selecting previously unselected package python3-urllib3. Preparing to unpack .../094-python3-urllib3_2.3.0-1_all.deb ... Unpacking python3-urllib3 (2.3.0-1) ... Selecting previously unselected package python3-dulwich. Preparing to unpack .../095-python3-dulwich_0.22.7-1_i386.deb ... Unpacking python3-dulwich (0.22.7-1) ... Selecting previously unselected package python3-itsdangerous. Preparing to unpack .../096-python3-itsdangerous_2.2.0-2_all.deb ... Unpacking python3-itsdangerous (2.2.0-2) ... Selecting previously unselected package python3-markupsafe. Preparing to unpack .../097-python3-markupsafe_2.1.5-1+b2_i386.deb ... Unpacking python3-markupsafe (2.1.5-1+b2) ... Selecting previously unselected package python3-jinja2. Preparing to unpack .../098-python3-jinja2_3.1.5-1_all.deb ... Unpacking python3-jinja2 (3.1.5-1) ... Selecting previously unselected package python3-werkzeug. Preparing to unpack .../099-python3-werkzeug_3.1.3-2_all.deb ... Unpacking python3-werkzeug (3.1.3-2) ... Selecting previously unselected package python3-flask. Preparing to unpack .../100-python3-flask_3.1.0-2_all.deb ... Unpacking python3-flask (3.1.0-2) ... Selecting previously unselected package python3-idna. Preparing to unpack .../101-python3-idna_3.8-2_all.deb ... Unpacking python3-idna (3.8-2) ... Selecting previously unselected package python3-iniconfig. Preparing to unpack .../102-python3-iniconfig_1.1.1-2_all.deb ... Unpacking python3-iniconfig (1.1.1-2) ... Selecting previously unselected package python3-legacy-cgi. Preparing to unpack .../103-python3-legacy-cgi_2.6.1-2_all.deb ... Unpacking python3-legacy-cgi (2.6.1-2) ... Selecting previously unselected package python3-markdown. Preparing to unpack .../104-python3-markdown_3.7-2_all.deb ... Unpacking python3-markdown (3.7-2) ... Selecting previously unselected package python3-openssl. Preparing to unpack .../105-python3-openssl_25.0.0-1_all.deb ... Unpacking python3-openssl (25.0.0-1) ... Selecting previously unselected package python3-packaging. Preparing to unpack .../106-python3-packaging_24.2-1_all.deb ... Unpacking python3-packaging (24.2-1) ... Selecting previously unselected package python3-tempita. Preparing to unpack .../107-python3-tempita_0.6.0-1_all.deb ... Unpacking python3-tempita (0.6.0-1) ... Selecting previously unselected package python3-paste. Preparing to unpack .../108-python3-paste_3.10.1-1_all.deb ... Unpacking python3-paste (3.10.1-1) ... Selecting previously unselected package python3-pastedeploy-tpl. Preparing to unpack .../109-python3-pastedeploy-tpl_3.1-1_all.deb ... Unpacking python3-pastedeploy-tpl (3.1-1) ... Selecting previously unselected package python3-pastedeploy. Preparing to unpack .../110-python3-pastedeploy_3.1-1_all.deb ... Unpacking python3-pastedeploy (3.1-1) ... Selecting previously unselected package python3-pluggy. Preparing to unpack .../111-python3-pluggy_1.5.0-1_all.deb ... Unpacking python3-pluggy (1.5.0-1) ... Selecting previously unselected package python3-pygments. Preparing to unpack .../112-python3-pygments_2.18.0+dfsg-2_all.deb ... Unpacking python3-pygments (2.18.0+dfsg-2) ... Selecting previously unselected package python3-pytest. Preparing to unpack .../113-python3-pytest_8.3.4-1_all.deb ... Unpacking python3-pytest (8.3.4-1) ... Selecting previously unselected package python3-requests. Preparing to unpack .../114-python3-requests_2.32.3+dfsg-1_all.deb ... Unpacking python3-requests (2.32.3+dfsg-1) ... Selecting previously unselected package python3-waitress. Preparing to unpack .../115-python3-waitress_3.0.2-1_all.deb ... Unpacking python3-waitress (3.0.2-1) ... Selecting previously unselected package python3-webob. Preparing to unpack .../116-python3-webob_1%3a1.8.9-1_all.deb ... Unpacking python3-webob (1:1.8.9-1) ... Selecting previously unselected package python3-webtest. Preparing to unpack .../117-python3-webtest_3.0.2-1_all.deb ... Unpacking python3-webtest (3.0.2-1) ... Selecting previously unselected package python3-yapsy. Preparing to unpack .../118-python3-yapsy_1.12.2-3_all.deb ... Unpacking python3-yapsy (1.12.2-3) ... Setting up media-types (10.1.0) ... Setting up libpipeline1:i386 (1.5.8-1) ... Setting up libkeyutils1:i386 (1.6.3-4) ... Setting up libicu72:i386 (72.1-6) ... Setting up bsdextrautils (2.40.4-2) ... Setting up libmagic-mgc (1:5.45-3+b1) ... Setting up libarchive-zip-perl (1.68-1) ... Setting up libtirpc-common (1.3.4+ds-1.3) ... Setting up libdebhelper-perl (13.24.1) ... Setting up libbrotli1:i386 (1.1.0-2+b6) ... Setting up libmagic1t64:i386 (1:5.45-3+b1) ... Setting up libpython3.12-minimal:i386 (3.12.8-5+b1) ... Setting up libnghttp2-14:i386 (1.64.0-1) ... Setting up gettext-base (0.23.1-1) ... Setting up m4 (1.4.19-5) ... Setting up libcom-err2:i386 (1.47.2-1) ... Setting up file (1:5.45-3+b1) ... Setting up locales (2.40-6) ... locales-all installed, skipping locales generation Setting up libelf1t64:i386 (0.192-4) ... Setting up libkrb5support0:i386 (1.21.3-4) ... Setting up libsasl2-modules-db:i386 (2.1.28+dfsg1-8+b1) ... Setting up tzdata (2024b-6) ... Current default time zone: 'Etc/UTC' Local time is now: Sun Feb 2 06:47:55 UTC 2025. Universal Time is now: Sun Feb 2 06:47:55 UTC 2025. Run 'dpkg-reconfigure tzdata' if you wish to change it. Setting up liberror-perl (0.17029-2) ... Setting up autotools-dev (20220109.1) ... Setting up localehelper (0.1.4-3.1) ... Setting up python3-pastedeploy-tpl (3.1-1) ... Setting up libunistring5:i386 (1.3-1) ... Setting up autopoint (0.23.1-1) ... Setting up libk5crypto3:i386 (1.21.3-4) ... Setting up libsasl2-2:i386 (2.1.28+dfsg1-8+b1) ... Setting up autoconf (2.72-3) ... Setting up libnghttp3-9:i386 (1.6.0-2) ... Setting up libffi8:i386 (3.4.6-1) ... Setting up dwz (0.15-1+b1) ... Setting up sensible-utils (0.0.24) ... Setting up libuchardet0:i386 (0.0.8-1+b2) ... Setting up libtasn1-6:i386 (4.19.0-3+b3) ... Setting up git-man (1:2.47.2-0.1) ... Setting up netbase (6.4) ... Setting up libngtcp2-16:i386 (1.9.1-1) ... Setting up libkrb5-3:i386 (1.21.3-4) ... Setting up libssh2-1t64:i386 (1.11.1-1) ... Setting up openssl (3.4.0-2) ... Setting up readline-common (8.2-6) ... Setting up libxml2:i386 (2.12.7+dfsg+really2.9.14-0.2+b1) ... Setting up libldap2:i386 (2.6.9+dfsg-1) ... Setting up automake (1:1.16.5-1.3) ... update-alternatives: using /usr/bin/automake-1.16 to provide /usr/bin/automake (automake) in auto mode Setting up libfile-stripnondeterminism-perl (1.14.1-1) ... Setting up python3.12-minimal (3.12.8-5+b1) ... Setting up gettext (0.23.1-1) ... Setting up libtool (2.5.4-2) ... Setting up libidn2-0:i386 (2.3.7-2+b1) ... Setting up intltool-debian (0.35.0+20060710.6) ... Setting up dh-autoreconf (20) ... Setting up ca-certificates (20241223) ... Updating certificates in /etc/ssl/certs... 152 added, 0 removed; done. Setting up libp11-kit0:i386 (0.25.5-3) ... Setting up libgssapi-krb5-2:i386 (1.21.3-4) ... Setting up libreadline8t64:i386 (8.2-6) ... Setting up dh-strip-nondeterminism (1.14.1-1) ... Setting up groff-base (1.23.0-7) ... Setting up libpython3.13-stdlib:i386 (3.13.1-3+b1) ... Setting up libpython3-stdlib:i386 (3.13.1-2) ... Setting up libgnutls30t64:i386 (3.8.8-2) ... Setting up libtirpc3t64:i386 (1.3.4+ds-1.3+b1) ... Setting up python3.13 (3.13.1-3+b1) ... Setting up po-debconf (1.0.21+nmu1) ... Setting up libpsl5t64:i386 (0.21.2-1.1+b1) ... Setting up python3 (3.13.1-2) ... Setting up python3-zipp (3.21.0-1) ... Setting up python3-click (8.1.8-1) ... Setting up python3-autocommand (2.2.2-3) ... Setting up man-db (2.13.0-1) ... Not building database; man-db/auto-update is not 'true'. Setting up python3-markupsafe (2.1.5-1+b2) ... Setting up python3-jinja2 (3.1.5-1) ... Setting up python3-pygments (2.18.0+dfsg-2) ... Setting up python3-tempita (0.6.0-1) ... Setting up python3-packaging (24.2-1) ... Setting up python3-ansi (0.1.5-2) ... Setting up python3-chardet (5.2.0+dfsg-2) ... Setting up python3-certifi (2024.12.14+ds-1) ... Setting up librtmp1:i386 (2.4+20151223.gitfa8646d.1-2+b5) ... Setting up python3-werkzeug (3.1.3-2) ... Setting up python3-idna (3.8-2) ... Setting up python3-markdown (3.7-2) ... Setting up python3-typing-extensions (4.12.2-2) ... Setting up python3-urllib3 (2.3.0-1) ... Setting up python3-pluggy (1.5.0-1) ... Setting up python3-legacy-cgi (2.6.1-2) ... Setting up python3-dulwich (0.22.7-1) ... Setting up libnsl2:i386 (1.3.0-3+b3) ... Setting up python3-soupsieve (2.6-1) ... Setting up libngtcp2-crypto-gnutls8:i386 (1.9.1-1) ... Setting up python3-cffi-backend:i386 (1.17.1-2+b1) ... Setting up python3-webob (1:1.8.9-1) ... Setting up python3-yapsy (1.12.2-3) ... Setting up python3-blinker (1.9.0-1) ... Setting up python3-daemonize (2.5.0-3) ... Setting up python3-more-itertools (10.6.0-1) ... Setting up python3-iniconfig (1.1.1-2) ... Setting up python3-waitress (3.0.2-1) ... Setting up python3-jaraco.functools (4.1.0-1) ... Setting up libpython3.12-stdlib:i386 (3.12.8-5+b1) ... Setting up python3-bottle (0.13.2-1.1) ... Setting up python3-jaraco.context (6.0.0-1) ... Setting up python3-colorlog (6.9.0-1) ... Setting up python3-charset-normalizer (3.4.0-1+b1) ... Setting up python3-pytest (8.3.4-1) ... Setting up python3-bcrypt (4.2.0-2.1) ... Setting up python3.12 (3.12.8-5+b1) ... Setting up python3-typeguard (4.4.1-1) ... Setting up libcurl3t64-gnutls:i386 (8.11.1-1+b1) ... Setting up python3-itsdangerous (2.2.0-2) ... Setting up python3-all (3.13.1-2) ... Setting up debhelper (13.24.1) ... Setting up python3-bs4 (4.12.3-3) ... Setting up python3-inflect (7.3.1-2) ... Setting up python3-jaraco.text (4.0.0-1) ... Setting up python3-cryptography (43.0.0-1) ... Setting up python3-requests (2.32.3+dfsg-1) ... Setting up git (1:2.47.2-0.1) ... Setting up python3-pkg-resources (75.6.0-1) ... Setting up python3-setuptools (75.6.0-1) ... Setting up python3-openssl (25.0.0-1) ... Setting up python3-flask (3.1.0-2) ... Setting up python3-paste (3.10.1-1) ... Setting up dh-python (6.20250108) ... Setting up python3-pastedeploy (3.1-1) ... Setting up python3-webtest (3.0.2-1) ... Processing triggers for libc-bin (2.40-6) ... Processing triggers for ca-certificates (20241223) ... Updating certificates in /etc/ssl/certs... 0 added, 0 removed; done. Running hooks in /etc/ca-certificates/update.d... done. Reading package lists... Building dependency tree... Reading state information... Reading extended state information... Initializing package states... Writing extended state information... Building tag database... -> Finished parsing the build-deps I: Building the package I: Running cd /build/reproducible-path/errbot-6.2.0+ds/ && env PATH="/usr/sbin:/usr/bin:/sbin:/bin:/usr/games" HOME="/nonexistent/first-build" dpkg-buildpackage -us -uc -b && env PATH="/usr/sbin:/usr/bin:/sbin:/bin:/usr/games" HOME="/nonexistent/first-build" dpkg-genchanges -S > ../errbot_6.2.0+ds-4_source.changes dpkg-buildpackage: info: source package errbot dpkg-buildpackage: info: source version 6.2.0+ds-4 dpkg-buildpackage: info: source distribution unstable dpkg-buildpackage: info: source changed by Alexandre Detiste dpkg-source --before-build . dpkg-buildpackage: info: host architecture i386 debian/rules clean dh clean --buildsystem=pybuild dh_auto_clean -O--buildsystem=pybuild pybuild --clean -i python{version} -p "3.12 3.13" I: pybuild base:311: python3.12 setup.py clean /usr/lib/python3/dist-packages/setuptools/_distutils/dist.py:261: UserWarning: Unknown distribution option: 'tests_require' warnings.warn(msg) running clean removing '/build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build' (and everything under it) 'build/bdist.linux-i686' does not exist -- can't clean it 'build/scripts-3.12' does not exist -- can't clean it I: pybuild base:311: python3.13 setup.py clean /usr/lib/python3/dist-packages/setuptools/_distutils/dist.py:261: UserWarning: Unknown distribution option: 'tests_require' warnings.warn(msg) running clean removing '/build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build' (and everything under it) 'build/bdist.linux-i686' does not exist -- can't clean it 'build/scripts-3.13' does not exist -- can't clean it rm -rf .pybuild/ find . -name \*.pyc -exec rm {} \; dh_autoreconf_clean -O--buildsystem=pybuild dh_clean -O--buildsystem=pybuild rm -f debian/debhelper-build-stamp rm -rf debian/.debhelper/ rm -f -- debian/errbot.substvars debian/files rm -fr -- debian/errbot/ debian/tmp/ find . \( \( \ \( -path .\*/.git -o -path .\*/.svn -o -path .\*/.bzr -o -path .\*/.hg -o -path .\*/CVS -o -path .\*/.pc -o -path .\*/_darcs \) -prune -o -type f -a \ \( -name '#*#' -o -name '.*~' -o -name '*~' -o -name DEADJOE \ -o -name '*.orig' -o -name '*.rej' -o -name '*.bak' \ -o -name '.*.orig' -o -name .*.rej -o -name '.SUMS' \ -o -name TAGS -o \( -path '*/.deps/*' -a -name '*.P' \) \ \) -exec rm -f {} + \) -o \ \( -type d -a \( -name autom4te.cache -o -name __pycache__ \) -prune -exec rm -rf {} + \) \) debian/rules binary dh binary --buildsystem=pybuild dh_update_autotools_config -O--buildsystem=pybuild dh_autoreconf -O--buildsystem=pybuild dh_auto_configure -O--buildsystem=pybuild pybuild --configure -i python{version} -p "3.12 3.13" I: pybuild base:311: python3.12 setup.py config /usr/lib/python3/dist-packages/setuptools/_distutils/dist.py:261: UserWarning: Unknown distribution option: 'tests_require' warnings.warn(msg) running config I: pybuild base:311: python3.13 setup.py config /usr/lib/python3/dist-packages/setuptools/_distutils/dist.py:261: UserWarning: Unknown distribution option: 'tests_require' warnings.warn(msg) running config dh_auto_build -O--buildsystem=pybuild pybuild --build -i python{version} -p "3.12 3.13" I: pybuild base:311: /usr/bin/python3.12 setup.py build /usr/lib/python3/dist-packages/setuptools/_distutils/dist.py:261: UserWarning: Unknown distribution option: 'tests_require' warnings.warn(msg) running build running build_py creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/repo_manager.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/bootstrap.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/plugin_info.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/streaming.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/botplugin.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/backend_plugin_manager.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/logs.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/flow.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/templating.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/version.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/core.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/plugin_wizard.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/config-template.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/plugin_manager.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/utils.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot copying ./errbot/cli.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/null.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/xmpp.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/base.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/test.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/text.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/irc.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/telegram_messenger.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/acls.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/backup.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/wsview.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/webserver.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/health.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/vcheck.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/cnf_filter.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/utils.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/chatRoom.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/help.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/textcmds.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/plugins.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/flows.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/rendering copying ./errbot/rendering/ansiext.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/rendering copying ./errbot/rendering/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/rendering copying ./errbot/rendering/xhtmlim.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/rendering creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/storage copying ./errbot/storage/memory.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/storage copying ./errbot/storage/shelf.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/storage copying ./errbot/storage/base.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/storage copying ./errbot/storage/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/storage copying ./errbot/backends/null.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/test.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/text.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/irc.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/xmpp.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends copying ./errbot/backends/telegram_messenger.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends/styles copying ./errbot/backends/styles/style.css -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends/styles copying ./errbot/backends/styles/style-demo.css -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends/styles creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends/images copying ./errbot/backends/images/errbot.svg -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends/images copying ./errbot/backends/images/prompt.svg -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends/images copying ./errbot/backends/images/errbot-bg.svg -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/backends/images copying ./errbot/core_plugins/chatRoom.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/help.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/utils.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/health.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/vcheck.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/flows.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/plugins.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/textcmds.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/cnf_filter.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/backup.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/webserver.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/acls.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/test.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/status.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/status_gc.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/about.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/webstatus.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/repos.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/plugin_info.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/webserver.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/status_plugins.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/status_load.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/repos2.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/core_plugins/templates copying ./errbot/storage/shelf.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/storage copying ./errbot/storage/memory.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/storage creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/templates/initdir copying ./errbot/templates/initdir/example.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/templates/initdir copying ./errbot/templates/initdir/example.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/templates/initdir copying ./errbot/templates/initdir/config.py.tmpl -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/templates/initdir copying ./errbot/templates/card.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/templates copying ./errbot/templates/new_plugin.py.tmpl -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build/errbot/templates I: pybuild base:311: /usr/bin/python3 setup.py build /usr/lib/python3/dist-packages/setuptools/_distutils/dist.py:261: UserWarning: Unknown distribution option: 'tests_require' warnings.warn(msg) running build running build_py creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/repo_manager.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/bootstrap.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/plugin_info.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/streaming.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/botplugin.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/backend_plugin_manager.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/logs.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/flow.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/templating.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/version.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/core.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/plugin_wizard.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/config-template.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/plugin_manager.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/utils.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot copying ./errbot/cli.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/null.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/xmpp.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/base.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/test.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/text.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/irc.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/telegram_messenger.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/acls.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/backup.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/wsview.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/webserver.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/health.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/vcheck.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/cnf_filter.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/utils.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/chatRoom.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/help.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/textcmds.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/plugins.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/flows.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/rendering copying ./errbot/rendering/ansiext.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/rendering copying ./errbot/rendering/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/rendering copying ./errbot/rendering/xhtmlim.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/rendering creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/storage copying ./errbot/storage/memory.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/storage copying ./errbot/storage/shelf.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/storage copying ./errbot/storage/base.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/storage copying ./errbot/storage/__init__.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/storage copying ./errbot/backends/null.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/test.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/text.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/irc.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/xmpp.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends copying ./errbot/backends/telegram_messenger.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends/styles copying ./errbot/backends/styles/style.css -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends/styles copying ./errbot/backends/styles/style-demo.css -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends/styles creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends/images copying ./errbot/backends/images/errbot.svg -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends/images copying ./errbot/backends/images/prompt.svg -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends/images copying ./errbot/backends/images/errbot-bg.svg -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/backends/images copying ./errbot/core_plugins/chatRoom.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/help.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/utils.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/health.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/vcheck.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/flows.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/plugins.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/textcmds.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/cnf_filter.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/backup.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/webserver.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/acls.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins copying ./errbot/core_plugins/test.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/status.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/status_gc.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/about.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/webstatus.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/repos.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/plugin_info.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/webserver.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/status_plugins.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/status_load.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/core_plugins/templates/repos2.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/core_plugins/templates copying ./errbot/storage/shelf.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/storage copying ./errbot/storage/memory.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/storage creating /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/templates/initdir copying ./errbot/templates/initdir/example.py -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/templates/initdir copying ./errbot/templates/initdir/example.plug -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/templates/initdir copying ./errbot/templates/initdir/config.py.tmpl -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/templates/initdir copying ./errbot/templates/card.md -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/templates copying ./errbot/templates/new_plugin.py.tmpl -> /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build/errbot/templates debian/rules override_dh_auto_test make[1]: Entering directory '/build/reproducible-path/errbot-6.2.0+ds' localehelper LANG=en_US.UTF-8 dh_auto_test pybuild --test --test-pytest -i python{version} -p "3.12 3.13" I: pybuild pybuild:308: rm -f /build/reproducible-path/errbot-6.2.0+ds/tests/backend_tests/slack_test.py I: pybuild base:311: cd /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build; python3.12 -m pytest -k "not test_broken_plugin and not test_backup and not test_plugin_cycle and not test_entrypoint_paths and not test_check_dependencies_requirements_file_all_installed" ============================= test session starts ============================== platform linux -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0 rootdir: /build/reproducible-path/errbot-6.2.0+ds plugins: typeguard-4.4.1 collected 215 items / 8 deselected / 207 selected tests/backend_manager_test.py ... [ 1%] tests/backend_tests/text_test.py . [ 1%] tests/base_backend_test.py ................................. [ 17%] tests/cascade_dependencies_test.py . [ 18%] tests/circular_dependencies_test.py . [ 18%] tests/commands_test.py ........EEEEEEEEEEEEEEEEEEEE [ 32%] tests/core_plugins_test.py EEE [ 33%] tests/core_test.py EE [ 34%] tests/dependencies_test.py EEEEEEE [ 38%] tests/dynaplug_test.py EEEEE [ 40%] tests/flow_e2e_test.py EEEEEEEEEEEEE [ 46%] tests/flow_test.py ... [ 48%] tests/i18n_test.py EEEE [ 50%] tests/link_test.py E [ 50%] tests/matchall_test.py EE [ 51%] tests/md_rendering_test.py .... [ 53%] tests/mention_test.py EEE [ 55%] tests/muc_test.py EEEEEE [ 57%] tests/multi_plugin_test.py EE [ 58%] tests/persistence_test.py .. [ 59%] tests/plugin_config_fail_test.py E [ 60%] tests/plugin_config_test.py .......E [ 64%] tests/plugin_info_test.py ........ [ 68%] tests/plugin_management_test.py ....... [ 71%] tests/poller_test.py EE [ 72%] tests/repo_manager_test.py ....... [ 75%] tests/simple_identifiers_test.py ..... [ 78%] tests/streaming_test.py F [ 78%] tests/syntax_test.py EEEE [ 80%] tests/templates_test.py EEEEE [ 83%] tests/utils_test.py .................... [ 92%] tests/webhooks_test.py EEEEEEEEEEEEEEE [100%] ==================================== ERRORS ==================================== _________________________ ERROR at setup of test_echo __________________________ self = , processes = 10 initializer = None, initargs = (), maxtasksperchild = None, context = None def __init__(self, processes=None, initializer=None, initargs=(), maxtasksperchild=None, context=None): # Attributes initialized early to make sure that they exist in # __del__() if __init__() raises an exception self._pool = [] self._state = INIT self._ctx = context or get_context() self._setup_queues() self._taskqueue = queue.SimpleQueue() # The _change_notifier queue exist to wake up self._handle_workers() # when the cache (self._cache) is empty or when there is a change in # the _state variable of the thread that runs _handle_workers. self._change_notifier = self._ctx.SimpleQueue() self._cache = _PoolCache(notifier=self._change_notifier) self._maxtasksperchild = maxtasksperchild self._initializer = initializer self._initargs = initargs if processes is None: processes = os.cpu_count() or 1 if processes < 1: raise ValueError("Number of processes must be at least 1") if maxtasksperchild is not None: if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0: raise ValueError("maxtasksperchild must be a positive int or None") if initializer is not None and not callable(initializer): raise TypeError('initializer must be a callable') self._processes = processes try: > self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:215: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = , processes = 10 initializer = None, initargs = (), maxtasksperchild = None, context = None def __init__(self, processes=None, initializer=None, initargs=(), maxtasksperchild=None, context=None): # Attributes initialized early to make sure that they exist in # __del__() if __init__() raises an exception self._pool = [] self._state = INIT self._ctx = context or get_context() self._setup_queues() self._taskqueue = queue.SimpleQueue() # The _change_notifier queue exist to wake up self._handle_workers() # when the cache (self._cache) is empty or when there is a change in # the _state variable of the thread that runs _handle_workers. self._change_notifier = self._ctx.SimpleQueue() self._cache = _PoolCache(notifier=self._change_notifier) self._maxtasksperchild = maxtasksperchild self._initializer = initializer self._initargs = initargs if processes is None: processes = os.cpu_count() or 1 if processes < 1: raise ValueError("Number of processes must be at least 1") if maxtasksperchild is not None: if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0: raise ValueError("maxtasksperchild must be a positive int or None") if initializer is not None and not callable(initializer): raise TypeError('initializer must be a callable') self._processes = processes try: self._repopulate_pool() except Exception: for p in self._pool: if p.exitcode is None: > p.terminate() E AttributeError: 'DummyProcess' object has no attribute 'terminate' /usr/lib/python3.12/multiprocessing/pool.py:219: AttributeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 219, in __init__ p.terminate() ^^^^^^^^^^^ AttributeError: 'DummyProcess' object has no attribute 'terminate' ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:54,037 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:54,039 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:54,039 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:54,041 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:54,041 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:54,042 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 219, in __init__ p.terminate() ^^^^^^^^^^^ AttributeError: 'DummyProcess' object has no attribute 'terminate' _______________________ ERROR at setup of test_status_gc _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:54,300 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:54,303 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:54,303 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:54,305 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:54,305 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:54,333 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _____________________ ERROR at setup of test_config_cycle ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:54,514 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:54,516 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:54,516 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:54,518 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:54,518 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:54,519 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________________ ERROR at setup of test_apropos ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:54,721 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:54,723 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:54,723 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:54,725 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:54,725 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:54,726 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________________ ERROR at setup of test_logtail ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:54,903 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:54,906 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:54,906 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:54,908 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:54,908 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:54,908 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________________ ERROR at setup of test_history ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:55,096 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:55,099 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:55,099 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:55,102 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:55,102 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:55,103 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _________________ ERROR at setup of test_encoding_preservation _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:55,311 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:55,314 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:55,314 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:55,317 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:55,317 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:55,318 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________ ERROR at setup of test_webserver_webhook_test _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:55,551 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:55,554 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:55,554 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:55,557 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:55,557 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:55,558 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________ ERROR at setup of test_activate_reload_and_deactivate _____________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:55,790 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:55,793 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:55,793 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:55,796 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:55,796 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:55,797 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________ ERROR at setup of test_unblacklist_and_blacklist _______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:56,027 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:56,030 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:56,030 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:56,033 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:56,033 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:56,034 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_optional_prefix ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:56,334 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:56,337 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:56,337 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:56,340 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:56,340 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:56,341 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________ ERROR at setup of test_optional_prefix_re_cmd _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:56,555 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:56,557 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:56,558 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:56,560 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:56,560 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:56,561 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _____________________ ERROR at setup of test_simple_match ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:56,732 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:56,734 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:56,734 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:56,736 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:56,736 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:56,737 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________ ERROR at setup of test_no_suggest_on_re_commands _______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:56,920 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:56,922 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:56,922 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:56,924 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:56,924 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:56,925 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_callback_no_command __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:57,130 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:57,133 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:57,133 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:57,136 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:57,136 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:57,136 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_subcommands ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:57,362 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:57,364 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:57,365 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:57,367 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:57,368 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:57,368 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______ ERROR at setup of test_command_not_found_with_space_in_bot_prefix _______ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:57,602 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:57,605 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:57,606 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:57,608 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:57,609 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:57,609 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_mock_injection _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:57,842 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:57,844 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:57,844 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:57,846 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:57,846 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:57,847 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ___________________ ERROR at setup of test_multiline_command ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:58,046 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:58,049 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:58,049 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:58,053 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:58,053 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:58,053 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_plugin_info_command __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:58,254 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:58,257 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:58,257 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:58,259 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:58,259 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:58,260 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_help_is_still_here ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'CORE_PLUGINS': ('Help', 'Utils', 'CommandNotFoundFilter'), 'BOT_ALT_PREFIXES': ('!',), 'BOT_PREFIX': '$'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:58,458 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:58,461 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:58,461 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:58,464 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:58,464 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:58,464 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_echo_still_here ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'CORE_PLUGINS': ('Help', 'Utils', 'CommandNotFoundFilter'), 'BOT_ALT_PREFIXES': ('!',), 'BOT_PREFIX': '$'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:58,678 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:58,681 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:58,681 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:58,684 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:58,684 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:58,685 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_bot_prefix_replaced __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'CORE_PLUGINS': ('Help', 'Utils', 'CommandNotFoundFilter'), 'BOT_ALT_PREFIXES': ('!',), 'BOT_PREFIX': '$'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:58,888 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:58,890 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:58,891 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:58,893 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:58,893 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:58,894 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ___________________ ERROR at setup of test_admins_to_notify ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'BOT_ADMINS_NOTIFICATIONS': 'zoni@localdomain'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:59,097 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:59,100 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:59,100 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:59,102 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:59,102 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:59,103 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_admins_not_notified __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'BOT_ADMINS_NOTIFICATIONS': 'zoni@localdomain'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:59,321 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:59,323 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:59,323 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:59,326 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:59,326 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:59,326 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________ ERROR at setup of test_if_all_loaded_by_default ________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:59,536 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:59,539 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:59,539 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:59,541 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:59,542 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:59,542 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ___________________ ERROR at setup of test_single_dependency ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:48:59,756 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:48:59,758 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:48:59,759 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:48:59,835 DEBUG errbot.core ErrBot init. 2025-02-01 18:48:59,835 DEBUG errbot.backends.base Backend init. 2025-02-01 18:48:59,836 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ___________________ ERROR at setup of test_double_dependency ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:00,054 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:00,057 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:00,057 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:00,060 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:00,060 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:00,061 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _________________ ERROR at setup of test_dependency_retrieval __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:00,281 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:00,284 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:00,284 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:00,287 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:00,287 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:00,288 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________ ERROR at setup of test_direct_circular_dependency _______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:00,490 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:00,492 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:00,492 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:00,495 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:00,495 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:00,496 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _____________ ERROR at setup of test_indirect_circular_dependency ______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:00,693 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:00,696 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:00,696 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:00,699 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:00,700 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:00,700 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_chained_dependency ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:00,907 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:00,910 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:00,911 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:00,914 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:00,914 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:00,915 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________________ ERROR at setup of test_simple _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:01,138 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:01,140 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:01,141 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:01,144 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:01,144 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:01,145 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________________ ERROR at setup of test_arg __________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:01,352 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:01,355 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:01,355 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:01,358 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:01,358 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:01,359 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________________ ERROR at setup of test_re ___________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:01,563 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:01,566 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:01,566 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:01,569 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:01,569 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:01,570 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________________ ERROR at setup of test_saw __________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:01,770 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:01,772 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:01,772 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:01,775 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:01,776 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:01,776 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________________ ERROR at setup of test_clashing ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:01,997 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:01,999 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:02,000 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:02,003 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:02,003 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:02,004 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_list_flows _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:02,235 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:02,238 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:02,238 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:02,242 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:02,242 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:02,242 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_no_autotrigger _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:02,462 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:02,464 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:02,464 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:02,468 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:02,468 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:02,469 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_autotrigger ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:02,658 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:02,661 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:02,661 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:02,664 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:02,664 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:02,664 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________ ERROR at setup of test_no_duplicate_autotrigger ________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:02,865 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:02,868 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:02,868 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:02,871 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:02,871 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:02,872 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _________________ ERROR at setup of test_secondary_autotrigger _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:03,168 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:03,171 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:03,171 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:03,174 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:03,175 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:03,175 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_manual_flow ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:03,408 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:03,411 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:03,411 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:03,415 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:03,416 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:03,416 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________ ERROR at setup of test_manual_flow_with_or_without_hinting __________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:03,645 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:03,648 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:03,648 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:03,652 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:03,652 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:03,652 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _________________ ERROR at setup of test_no_flyby_trigger_flow _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:03,859 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:03,861 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:03,861 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:03,865 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:03,865 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:03,865 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________________ ERROR at setup of test_flow_only _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:04,082 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:04,085 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:04,085 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:04,089 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:04,089 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:04,090 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_flow_only_help _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:04,311 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:04,313 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:04,314 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:04,317 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:04,317 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:04,318 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_flows_stop _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:04,527 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:04,529 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:04,530 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:04,533 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:04,534 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:04,534 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_flows_kill _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:04,743 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:04,745 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:04,746 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:04,749 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:04,749 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:04,750 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________________ ERROR at setup of test_room_flow _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:04,954 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:04,957 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:04,957 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:04,961 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:04,961 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:04,962 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_i18n_return ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:05,163 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:05,165 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:05,165 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:05,170 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:05,170 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:05,170 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ___________________ ERROR at setup of test_i18n_simple_name ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:05,371 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:05,374 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:05,374 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:05,378 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:05,378 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:05,379 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_i18n_prefix ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:05,575 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:05,578 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:05,578 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:05,581 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:05,581 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:05,582 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_i18n_suffix ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:05,785 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:05,787 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:05,787 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:05,790 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:05,790 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:05,791 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_linked_plugin_here ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:05,977 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:05,979 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:05,979 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:05,983 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:05,983 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:05,984 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ___________________ ERROR at setup of test_botmatch_correct ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:06,183 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:06,185 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:06,185 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:06,189 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:06,189 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:06,189 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________________ ERROR at setup of test_botmatch ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:06,476 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:06,479 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:06,479 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:06,482 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:06,483 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:06,483 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_foreign_mention ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:06,706 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:06,709 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:06,709 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:06,712 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:06,713 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:06,713 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_testbot_mention ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:06,907 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:06,909 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:06,909 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:06,913 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:06,913 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:06,914 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ___________________ ERROR at setup of test_multiple_mentions ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:07,103 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:07,106 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:07,106 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:07,110 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:07,110 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:07,111 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_plugin_methods _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:07,308 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:07,310 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:07,310 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:07,314 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:07,314 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:07,314 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________ ERROR at setup of test_create_join_leave_destroy_lifecycle __________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:07,509 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:07,511 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:07,512 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:07,515 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:07,516 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:07,516 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________________ ERROR at setup of test_occupants _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:07,698 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:07,701 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:07,701 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:07,705 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:07,706 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:07,706 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _________________________ ERROR at setup of test_topic _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:07,890 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:07,893 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:07,893 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:07,896 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:07,896 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:07,897 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ___________________ ERROR at setup of test_plugin_callbacks ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:08,081 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:08,083 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:08,084 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:08,087 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:08,087 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:08,088 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_botcommands ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:08,288 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:08,290 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:08,290 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:08,294 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:08,294 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:08,295 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _________________________ ERROR at setup of test_first _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:08,474 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:08,477 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:08,477 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:08,481 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:08,481 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:08,482 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________________ ERROR at setup of test_second _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:08,660 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:08,662 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:08,662 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:08,666 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:08,666 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:08,667 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _____________________ ERROR at setup of test_failed_config _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:08,855 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:08,857 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:08,857 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:08,861 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:08,861 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:08,862 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _____________________ ERROR at setup of test_failed_config _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:09,073 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:09,075 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:09,075 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:09,078 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:09,078 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:09,079 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _____________________ ERROR at setup of test_delayed_hello _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:09,301 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:09,304 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:09,304 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:09,392 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:09,393 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:09,393 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_delayed_hello_loop ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:09,568 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:09,571 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:09,571 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:09,574 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:09,574 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:09,575 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________________ ERROR at setup of test_nosyntax ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:09,806 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:09,808 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:09,808 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:09,811 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:09,811 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:09,812 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________________ ERROR at setup of test_syntax _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:09,980 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:09,982 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:09,982 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:09,986 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:09,986 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:09,986 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________________ ERROR at setup of test_re_syntax _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:10,152 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:10,154 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:10,154 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:10,158 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:10,158 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:10,158 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_arg_syntax _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:10,333 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:10,335 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:10,336 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:10,339 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:10,339 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:10,340 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_1 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:10,513 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:10,515 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:10,515 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:10,518 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:10,519 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:10,519 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_2 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:10,694 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:10,697 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:10,697 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:10,700 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:10,700 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:10,701 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_3 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:10,877 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:10,879 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:10,879 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:10,883 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:10,883 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:10,883 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_4 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:11,062 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:11,064 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:11,064 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:11,068 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:11,068 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:11,068 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_5 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:11,247 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:11,249 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:11,249 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:11,253 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:11,253 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:11,254 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________ ERROR at setup of test_not_configured_url_returns_404 _____________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:11,462 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:11,464 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:11,464 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:11,468 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:11,468 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:11,469 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread __________________ ERROR at setup of test_webserver_plugin_ok __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:11,644 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:11,646 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:11,646 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:11,650 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:11,650 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:11,650 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _________________ ERROR at setup of test_trailing_no_slash_ok __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:11,820 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:11,822 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:11,822 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:11,826 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:11,826 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:11,826 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ________________ ERROR at setup of test_trailing_slash_also_ok _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:12,107 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:12,109 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:12,109 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:12,112 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:12,112 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:12,113 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _____________ ERROR at setup of test_json_is_automatically_decoded _____________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:12,284 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:12,286 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:12,286 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:12,290 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:12,290 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:12,291 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ______ ERROR at setup of test_json_on_custom_url_is_automatically_decoded ______ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:12,463 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:12,465 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:12,465 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:12,468 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:12,468 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:12,469 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _ ERROR at setup of test_post_form_on_webhook_without_form_param_is_automatically_decoded _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:12,643 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:12,645 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:12,645 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:12,648 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:12,649 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:12,649 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _ ERROR at setup of test_post_form_on_webhook_with_custom_url_and_without_form_param_is_automatically_decoded _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:12,831 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:12,833 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:12,833 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:12,836 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:12,836 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:12,837 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _ ERROR at setup of test_webhooks_with_form_parameter_decode_json_automatically _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:13,008 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:13,010 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:13,011 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:13,014 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:13,014 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:13,015 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _ ERROR at setup of test_webhooks_with_form_parameter_on_custom_url_decode_json_automatically _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:13,185 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:13,187 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:13,187 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:13,191 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:13,191 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:13,191 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______________ ERROR at setup of test_webhooks_with_raw_request _______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:13,368 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:13,370 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:13,370 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:13,373 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:13,373 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:13,374 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______ ERROR at setup of test_webhooks_with_naked_decorator_raw_request _______ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:13,550 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:13,552 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:13,552 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:13,556 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:13,556 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:13,556 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread _______ ERROR at setup of test_generate_certificate_creates_usable_cert ________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:13,743 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:13,745 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:13,745 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:13,749 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:13,750 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:13,750 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________ ERROR at setup of test_custom_headers_and_status_codes ____________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:13,921 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:13,923 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:13,923 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:13,926 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:13,926 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:13,927 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ____________________ ERROR at setup of test_lambda_webhook _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.12/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.12/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.12/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.12/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.12/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:14,101 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:14,103 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:14,103 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:14,106 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:14,107 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:14,107 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() ^^^^^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) ^^^^^^^^^^^^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) File "/usr/lib/python3.12/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() File "/usr/lib/python3.12/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() File "/usr/lib/python3.12/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) File "/usr/lib/python3.12/threading.py", line 994, in start _start_new_thread(self._bootstrap, ()) RuntimeError: can't start new thread =================================== FAILURES =================================== ________________________________ test_streaming ________________________________ def test_streaming(): canary = b"this is my test" * 1000 source = Stream(TestPerson("gbin@gootz.net"), BytesIO(canary)) clients = [StreamingClient() for _ in range(50)] > Tee(source, clients).run() tests/streaming_test.py:17: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/streaming.py:81: in run thread.start() _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: > _start_new_thread(self._bootstrap, ()) E RuntimeError: can't start new thread /usr/lib/python3.12/threading.py:994: RuntimeError =============================== warnings summary =============================== ../../../errbot/utils.py:13 /build/reproducible-path/errbot-6.2.0+ds/errbot/utils.py:13: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html import pkg_resources .pybuild/cpython3_3.12_errbot/build/tests/cascade_dependencies_test.py::test_dependency_commands /usr/lib/python3/dist-packages/webob/compat.py:5: DeprecationWarning: 'cgi' is deprecated and slated for removal in Python 3.13 from cgi import parse_header -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html =========================== short test summary info ============================ FAILED tests/streaming_test.py::test_streaming - RuntimeError: can't start ne... ERROR tests/commands_test.py::test_echo - SystemExit: -1 ERROR tests/commands_test.py::test_status_gc - SystemExit: -1 ERROR tests/commands_test.py::test_config_cycle - SystemExit: -1 ERROR tests/commands_test.py::test_apropos - SystemExit: -1 ERROR tests/commands_test.py::test_logtail - SystemExit: -1 ERROR tests/commands_test.py::test_history - SystemExit: -1 ERROR tests/commands_test.py::test_encoding_preservation - SystemExit: -1 ERROR tests/commands_test.py::test_webserver_webhook_test - SystemExit: -1 ERROR tests/commands_test.py::test_activate_reload_and_deactivate - SystemExi... ERROR tests/commands_test.py::test_unblacklist_and_blacklist - SystemExit: -1 ERROR tests/commands_test.py::test_optional_prefix - SystemExit: -1 ERROR tests/commands_test.py::test_optional_prefix_re_cmd - SystemExit: -1 ERROR tests/commands_test.py::test_simple_match - SystemExit: -1 ERROR tests/commands_test.py::test_no_suggest_on_re_commands - SystemExit: -1 ERROR tests/commands_test.py::test_callback_no_command - SystemExit: -1 ERROR tests/commands_test.py::test_subcommands - SystemExit: -1 ERROR tests/commands_test.py::test_command_not_found_with_space_in_bot_prefix ERROR tests/commands_test.py::test_mock_injection - SystemExit: -1 ERROR tests/commands_test.py::test_multiline_command - SystemExit: -1 ERROR tests/commands_test.py::test_plugin_info_command - SystemExit: -1 ERROR tests/core_plugins_test.py::test_help_is_still_here - SystemExit: -1 ERROR tests/core_plugins_test.py::test_echo_still_here - SystemExit: -1 ERROR tests/core_plugins_test.py::test_bot_prefix_replaced - SystemExit: -1 ERROR tests/core_test.py::test_admins_to_notify - SystemExit: -1 ERROR tests/core_test.py::test_admins_not_notified - SystemExit: -1 ERROR tests/dependencies_test.py::test_if_all_loaded_by_default - SystemExit: -1 ERROR tests/dependencies_test.py::test_single_dependency - SystemExit: -1 ERROR tests/dependencies_test.py::test_double_dependency - SystemExit: -1 ERROR tests/dependencies_test.py::test_dependency_retrieval - SystemExit: -1 ERROR tests/dependencies_test.py::test_direct_circular_dependency - SystemExi... ERROR tests/dependencies_test.py::test_indirect_circular_dependency - SystemE... ERROR tests/dependencies_test.py::test_chained_dependency - SystemExit: -1 ERROR tests/dynaplug_test.py::test_simple - SystemExit: -1 ERROR tests/dynaplug_test.py::test_arg - SystemExit: -1 ERROR tests/dynaplug_test.py::test_re - SystemExit: -1 ERROR tests/dynaplug_test.py::test_saw - SystemExit: -1 ERROR tests/dynaplug_test.py::test_clashing - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_list_flows - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_no_autotrigger - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_autotrigger - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_no_duplicate_autotrigger - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_secondary_autotrigger - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_manual_flow - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_manual_flow_with_or_without_hinting - Syst... ERROR tests/flow_e2e_test.py::test_no_flyby_trigger_flow - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_flow_only - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_flow_only_help - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_flows_stop - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_flows_kill - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_room_flow - SystemExit: -1 ERROR tests/i18n_test.py::test_i18n_return - SystemExit: -1 ERROR tests/i18n_test.py::test_i18n_simple_name - SystemExit: -1 ERROR tests/i18n_test.py::test_i18n_prefix - SystemExit: -1 ERROR tests/i18n_test.py::test_i18n_suffix - SystemExit: -1 ERROR tests/link_test.py::test_linked_plugin_here - SystemExit: -1 ERROR tests/matchall_test.py::test_botmatch_correct - SystemExit: -1 ERROR tests/matchall_test.py::test_botmatch - SystemExit: -1 ERROR tests/mention_test.py::test_foreign_mention - SystemExit: -1 ERROR tests/mention_test.py::test_testbot_mention - SystemExit: -1 ERROR tests/mention_test.py::test_multiple_mentions - SystemExit: -1 ERROR tests/muc_test.py::test_plugin_methods - SystemExit: -1 ERROR tests/muc_test.py::test_create_join_leave_destroy_lifecycle - SystemExi... ERROR tests/muc_test.py::test_occupants - SystemExit: -1 ERROR tests/muc_test.py::test_topic - SystemExit: -1 ERROR tests/muc_test.py::test_plugin_callbacks - SystemExit: -1 ERROR tests/muc_test.py::test_botcommands - SystemExit: -1 ERROR tests/multi_plugin_test.py::test_first - SystemExit: -1 ERROR tests/multi_plugin_test.py::test_second - SystemExit: -1 ERROR tests/plugin_config_fail_test.py::test_failed_config - SystemExit: -1 ERROR tests/plugin_config_test.py::test_failed_config - SystemExit: -1 ERROR tests/poller_test.py::test_delayed_hello - SystemExit: -1 ERROR tests/poller_test.py::test_delayed_hello_loop - SystemExit: -1 ERROR tests/syntax_test.py::test_nosyntax - SystemExit: -1 ERROR tests/syntax_test.py::test_syntax - SystemExit: -1 ERROR tests/syntax_test.py::test_re_syntax - SystemExit: -1 ERROR tests/syntax_test.py::test_arg_syntax - SystemExit: -1 ERROR tests/templates_test.py::test_templates_1 - SystemExit: -1 ERROR tests/templates_test.py::test_templates_2 - SystemExit: -1 ERROR tests/templates_test.py::test_templates_3 - SystemExit: -1 ERROR tests/templates_test.py::test_templates_4 - SystemExit: -1 ERROR tests/templates_test.py::test_templates_5 - SystemExit: -1 ERROR tests/webhooks_test.py::test_not_configured_url_returns_404 - SystemExi... ERROR tests/webhooks_test.py::test_webserver_plugin_ok - SystemExit: -1 ERROR tests/webhooks_test.py::test_trailing_no_slash_ok - SystemExit: -1 ERROR tests/webhooks_test.py::test_trailing_slash_also_ok - SystemExit: -1 ERROR tests/webhooks_test.py::test_json_is_automatically_decoded - SystemExit... ERROR tests/webhooks_test.py::test_json_on_custom_url_is_automatically_decoded ERROR tests/webhooks_test.py::test_post_form_on_webhook_without_form_param_is_automatically_decoded ERROR tests/webhooks_test.py::test_post_form_on_webhook_with_custom_url_and_without_form_param_is_automatically_decoded ERROR tests/webhooks_test.py::test_webhooks_with_form_parameter_decode_json_automatically ERROR tests/webhooks_test.py::test_webhooks_with_form_parameter_on_custom_url_decode_json_automatically ERROR tests/webhooks_test.py::test_webhooks_with_raw_request - SystemExit: -1 ERROR tests/webhooks_test.py::test_webhooks_with_naked_decorator_raw_request ERROR tests/webhooks_test.py::test_generate_certificate_creates_usable_cert ERROR tests/webhooks_test.py::test_custom_headers_and_status_codes - SystemEx... ERROR tests/webhooks_test.py::test_lambda_webhook - SystemExit: -1 ===== 1 failed, 110 passed, 8 deselected, 2 warnings, 96 errors in 25.41s ====== E: pybuild pybuild:389: test: plugin distutils failed with: exit code=1: cd /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.12_errbot/build; python3.12 -m pytest -k "not test_broken_plugin and not test_backup and not test_plugin_cycle and not test_entrypoint_paths and not test_check_dependencies_requirements_file_all_installed" I: pybuild pybuild:308: rm -f /build/reproducible-path/errbot-6.2.0+ds/tests/backend_tests/slack_test.py I: pybuild base:311: cd /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build; python3.13 -m pytest -k "not test_broken_plugin and not test_backup and not test_plugin_cycle and not test_entrypoint_paths and not test_check_dependencies_requirements_file_all_installed" ============================= test session starts ============================== platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0 rootdir: /build/reproducible-path/errbot-6.2.0+ds plugins: typeguard-4.4.1 collected 215 items / 8 deselected / 207 selected tests/backend_manager_test.py ... [ 1%] tests/backend_tests/text_test.py . [ 1%] tests/base_backend_test.py ................................. [ 17%] tests/cascade_dependencies_test.py . [ 18%] tests/circular_dependencies_test.py . [ 18%] tests/commands_test.py ........EEEEEEEEEEEEEEEEEEEE [ 32%] tests/core_plugins_test.py EEE [ 33%] tests/core_test.py EE [ 34%] tests/dependencies_test.py EEEEEEE [ 38%] tests/dynaplug_test.py EEEEE [ 40%] tests/flow_e2e_test.py EEEEEEEEEEEEE [ 46%] tests/flow_test.py ... [ 48%] tests/i18n_test.py EEEE [ 50%] tests/link_test.py E [ 50%] tests/matchall_test.py EE [ 51%] tests/md_rendering_test.py .... [ 53%] tests/mention_test.py EEE [ 55%] tests/muc_test.py EEEEEE [ 57%] tests/multi_plugin_test.py EE [ 58%] tests/persistence_test.py .. [ 59%] tests/plugin_config_fail_test.py E [ 60%] tests/plugin_config_test.py .......E [ 64%] tests/plugin_info_test.py ........ [ 68%] tests/plugin_management_test.py ....... [ 71%] tests/poller_test.py EE [ 72%] tests/repo_manager_test.py ....... [ 75%] tests/simple_identifiers_test.py ..... [ 78%] tests/streaming_test.py F [ 78%] tests/syntax_test.py EEEE [ 80%] tests/templates_test.py EEEEE [ 83%] tests/utils_test.py .................... [ 92%] tests/webhooks_test.py EEEEEEEEEEEEEEE [100%] ==================================== ERRORS ==================================== _________________________ ERROR at setup of test_echo __________________________ self = , processes = 10 initializer = None, initargs = (), maxtasksperchild = None, context = None def __init__(self, processes=None, initializer=None, initargs=(), maxtasksperchild=None, context=None): # Attributes initialized early to make sure that they exist in # __del__() if __init__() raises an exception self._pool = [] self._state = INIT self._ctx = context or get_context() self._setup_queues() self._taskqueue = queue.SimpleQueue() # The _change_notifier queue exist to wake up self._handle_workers() # when the cache (self._cache) is empty or when there is a change in # the _state variable of the thread that runs _handle_workers. self._change_notifier = self._ctx.SimpleQueue() self._cache = _PoolCache(notifier=self._change_notifier) self._maxtasksperchild = maxtasksperchild self._initializer = initializer self._initargs = initargs if processes is None: processes = os.process_cpu_count() or 1 if processes < 1: raise ValueError("Number of processes must be at least 1") if maxtasksperchild is not None: if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0: raise ValueError("maxtasksperchild must be a positive int or None") if initializer is not None and not callable(initializer): raise TypeError('initializer must be a callable') self._processes = processes try: > self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:215: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = , processes = 10 initializer = None, initargs = (), maxtasksperchild = None, context = None def __init__(self, processes=None, initializer=None, initargs=(), maxtasksperchild=None, context=None): # Attributes initialized early to make sure that they exist in # __del__() if __init__() raises an exception self._pool = [] self._state = INIT self._ctx = context or get_context() self._setup_queues() self._taskqueue = queue.SimpleQueue() # The _change_notifier queue exist to wake up self._handle_workers() # when the cache (self._cache) is empty or when there is a change in # the _state variable of the thread that runs _handle_workers. self._change_notifier = self._ctx.SimpleQueue() self._cache = _PoolCache(notifier=self._change_notifier) self._maxtasksperchild = maxtasksperchild self._initializer = initializer self._initargs = initargs if processes is None: processes = os.process_cpu_count() or 1 if processes < 1: raise ValueError("Number of processes must be at least 1") if maxtasksperchild is not None: if not isinstance(maxtasksperchild, int) or maxtasksperchild <= 0: raise ValueError("maxtasksperchild must be a positive int or None") if initializer is not None and not callable(initializer): raise TypeError('initializer must be a callable') self._processes = processes try: self._repopulate_pool() except Exception: for p in self._pool: if p.exitcode is None: > p.terminate() E AttributeError: 'DummyProcess' object has no attribute 'terminate' /usr/lib/python3.13/multiprocessing/pool.py:219: AttributeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 219, in __init__ p.terminate() ^^^^^^^^^^^ AttributeError: 'DummyProcess' object has no attribute 'terminate' ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:22,853 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:22,855 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:22,855 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:22,858 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:22,858 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:22,859 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 219, in __init__ p.terminate() ^^^^^^^^^^^ AttributeError: 'DummyProcess' object has no attribute 'terminate' _______________________ ERROR at setup of test_status_gc _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:23,063 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:23,065 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:23,065 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:23,067 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:23,067 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:23,097 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _____________________ ERROR at setup of test_config_cycle ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:23,271 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:23,273 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:23,273 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:23,275 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:23,275 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:23,276 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________________ ERROR at setup of test_apropos ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:23,442 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:23,444 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:23,444 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:23,446 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:23,446 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:23,447 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________________ ERROR at setup of test_logtail ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:23,602 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:23,604 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:23,605 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:23,606 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:23,606 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:23,607 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________________ ERROR at setup of test_history ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:23,768 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:23,770 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:23,770 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:23,772 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:23,772 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:23,773 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _________________ ERROR at setup of test_encoding_preservation _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:23,935 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:23,937 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:23,937 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:23,939 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:23,939 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:23,939 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________ ERROR at setup of test_webserver_webhook_test _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:24,097 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:24,100 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:24,100 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:24,102 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:24,102 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:24,102 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________ ERROR at setup of test_activate_reload_and_deactivate _____________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:24,257 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:24,259 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:24,259 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:24,261 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:24,261 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:24,262 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________ ERROR at setup of test_unblacklist_and_blacklist _______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:24,423 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:24,425 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:24,425 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:24,426 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:24,427 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:24,427 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_optional_prefix ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:24,593 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:24,595 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:24,595 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:24,597 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:24,597 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:24,597 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________ ERROR at setup of test_optional_prefix_re_cmd _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:24,777 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:24,779 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:24,779 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:24,781 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:24,781 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:24,781 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _____________________ ERROR at setup of test_simple_match ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:24,941 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:24,943 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:24,943 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:24,945 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:24,945 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:24,945 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________ ERROR at setup of test_no_suggest_on_re_commands _______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:25,102 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:25,105 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:25,105 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:25,107 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:25,107 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:25,107 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_callback_no_command __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:25,268 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:25,271 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:25,271 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:25,273 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:25,273 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:25,273 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_subcommands ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:25,428 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:25,430 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:25,430 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:25,432 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:25,432 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:25,433 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______ ERROR at setup of test_command_not_found_with_space_in_bot_prefix _______ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:25,586 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:25,588 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:25,588 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:25,590 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:25,590 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:25,590 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_mock_injection _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:25,746 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:25,748 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:25,748 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:25,750 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:25,750 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:25,750 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ___________________ ERROR at setup of test_multiline_command ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:25,909 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:25,911 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:25,911 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:25,913 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:25,913 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:25,913 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_plugin_info_command __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:26,071 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:26,074 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:26,074 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:26,076 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:26,076 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:26,076 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_help_is_still_here ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'CORE_PLUGINS': ('Help', 'Utils', 'CommandNotFoundFilter'), 'BOT_ALT_PREFIXES': ('!',), 'BOT_PREFIX': '$'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:26,231 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:26,233 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:26,233 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:26,235 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:26,235 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:26,236 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_echo_still_here ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'CORE_PLUGINS': ('Help', 'Utils', 'CommandNotFoundFilter'), 'BOT_ALT_PREFIXES': ('!',), 'BOT_PREFIX': '$'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:26,395 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:26,397 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:26,397 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:26,399 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:26,399 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:26,399 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_bot_prefix_replaced __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'CORE_PLUGINS': ('Help', 'Utils', 'CommandNotFoundFilter'), 'BOT_ALT_PREFIXES': ('!',), 'BOT_PREFIX': '$'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:26,616 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:26,618 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:26,618 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:26,620 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:26,620 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:26,621 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ___________________ ERROR at setup of test_admins_to_notify ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'BOT_ADMINS_NOTIFICATIONS': 'zoni@localdomain'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:26,773 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:26,775 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:26,776 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:26,778 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:26,778 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:26,778 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_admins_not_notified __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- DEBUG errbot.backends.test Merging {'BOT_ADMINS_NOTIFICATIONS': 'zoni@localdomain'} to the bot config. INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:26,943 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:26,946 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:26,946 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:26,949 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:26,949 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:26,949 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________ ERROR at setup of test_if_all_loaded_by_default ________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:27,119 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:27,121 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:27,121 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:27,123 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:27,123 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:27,124 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ___________________ ERROR at setup of test_single_dependency ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:27,282 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:27,285 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:27,285 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:27,287 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:27,287 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:27,287 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ___________________ ERROR at setup of test_double_dependency ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:27,443 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:27,446 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:27,446 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:27,449 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:27,449 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:27,449 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _________________ ERROR at setup of test_dependency_retrieval __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:27,605 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:27,607 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:27,607 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:27,609 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:27,609 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:27,610 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________ ERROR at setup of test_direct_circular_dependency _______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:27,767 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:27,770 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:27,770 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:27,772 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:27,772 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:27,773 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _____________ ERROR at setup of test_indirect_circular_dependency ______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:27,934 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:27,936 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:27,937 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:27,939 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:27,939 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:27,939 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_chained_dependency ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:28,103 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:28,106 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:28,106 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:28,108 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:28,108 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:28,109 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________________ ERROR at setup of test_simple _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:28,270 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:28,272 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:28,272 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:28,274 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:28,275 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:28,275 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________________ ERROR at setup of test_arg __________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:28,427 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:28,429 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:28,429 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:28,431 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:28,431 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:28,432 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________________ ERROR at setup of test_re ___________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:28,587 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:28,589 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:28,589 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:28,592 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:28,592 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:28,592 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________________ ERROR at setup of test_saw __________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:28,744 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:28,747 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:28,747 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:28,749 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:28,749 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:28,750 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________________ ERROR at setup of test_clashing ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:28,906 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:28,908 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:28,908 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:28,910 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:28,910 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:28,911 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_list_flows _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:29,063 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:29,065 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:29,065 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:29,068 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:29,068 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:29,068 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_no_autotrigger _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:29,228 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:29,230 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:29,230 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:29,232 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:29,232 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:29,233 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_autotrigger ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:29,385 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:29,387 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:29,387 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:29,389 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:29,389 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:29,390 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________ ERROR at setup of test_no_duplicate_autotrigger ________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:29,544 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:29,546 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:29,546 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:29,549 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:29,549 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:29,549 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _________________ ERROR at setup of test_secondary_autotrigger _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:29,708 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:29,710 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:29,710 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:29,713 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:29,713 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:29,714 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_manual_flow ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:29,888 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:29,890 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:29,891 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:29,893 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:29,893 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:29,894 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________ ERROR at setup of test_manual_flow_with_or_without_hinting __________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:30,070 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:30,073 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:30,073 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:30,076 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:30,076 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:30,077 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _________________ ERROR at setup of test_no_flyby_trigger_flow _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:30,247 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:30,250 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:30,250 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:30,253 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:30,253 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:30,253 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________________ ERROR at setup of test_flow_only _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:30,406 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:30,408 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:30,408 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:30,410 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:30,410 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:30,411 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_flow_only_help _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:30,565 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:30,567 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:30,567 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:30,569 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:30,570 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:30,570 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_flows_stop _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:30,724 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:30,726 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:30,726 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:30,728 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:30,729 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:30,729 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_flows_kill _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:30,970 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:30,972 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:30,972 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:30,975 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:30,975 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:30,975 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________________ ERROR at setup of test_room_flow _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:31,130 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:31,132 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:31,132 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:31,135 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:31,135 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:31,135 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_i18n_return ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:31,293 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:31,295 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:31,295 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:31,298 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:31,298 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:31,299 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ___________________ ERROR at setup of test_i18n_simple_name ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:31,459 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:31,461 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:31,461 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:31,463 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:31,464 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:31,464 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_i18n_prefix ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:31,662 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:31,664 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:31,665 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:31,668 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:31,668 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:31,668 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_i18n_suffix ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:31,837 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:31,839 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:31,839 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:31,842 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:31,842 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:31,842 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_linked_plugin_here ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:31,998 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:32,000 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:32,000 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:32,003 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:32,003 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:32,004 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ___________________ ERROR at setup of test_botmatch_correct ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:32,192 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:32,195 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:32,195 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:32,198 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:32,198 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:32,198 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________________ ERROR at setup of test_botmatch ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:32,364 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:32,367 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:32,367 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:32,371 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:32,371 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:32,371 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_foreign_mention ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:32,597 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:32,600 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:32,600 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:32,604 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:32,604 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:32,605 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_testbot_mention ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:32,815 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:32,818 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:32,818 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:32,822 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:32,822 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:32,823 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ___________________ ERROR at setup of test_multiple_mentions ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:33,028 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:33,031 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:33,031 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:33,035 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:33,035 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:33,035 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_plugin_methods _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:33,253 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:33,256 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:33,256 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:33,259 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:33,259 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:33,260 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________ ERROR at setup of test_create_join_leave_destroy_lifecycle __________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:33,480 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:33,483 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:33,483 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:33,489 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:33,489 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:33,490 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________________ ERROR at setup of test_occupants _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:33,714 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:33,717 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:33,717 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:33,721 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:33,721 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:33,722 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _________________________ ERROR at setup of test_topic _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:33,932 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:33,935 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:33,935 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:33,939 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:33,939 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:33,939 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ___________________ ERROR at setup of test_plugin_callbacks ____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:34,155 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:34,158 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:34,159 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:34,163 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:34,163 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:34,164 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_botcommands ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:34,386 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:34,389 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:34,390 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:34,393 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:34,393 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:34,394 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _________________________ ERROR at setup of test_first _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:34,609 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:34,612 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:34,612 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:34,616 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:34,616 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:34,617 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________________ ERROR at setup of test_second _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:34,835 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:34,838 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:34,838 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:34,842 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:34,842 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:34,842 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _____________________ ERROR at setup of test_failed_config _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:35,059 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:35,062 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:35,062 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:35,066 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:35,066 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:35,067 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _____________________ ERROR at setup of test_failed_config _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:35,290 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:35,293 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:35,293 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:35,297 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:35,297 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:35,298 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _____________________ ERROR at setup of test_delayed_hello _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:35,558 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:35,561 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:35,562 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:35,566 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:35,566 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:35,567 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_delayed_hello_loop ___________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:35,786 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:35,789 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:35,789 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:35,792 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:35,792 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:35,793 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________________ ERROR at setup of test_nosyntax ________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:36,105 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:36,108 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:36,108 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:36,112 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:36,112 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:36,113 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________________ ERROR at setup of test_syntax _________________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:36,430 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:36,433 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:36,433 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:36,437 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:36,437 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:36,438 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________________ ERROR at setup of test_re_syntax _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:36,654 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:36,657 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:36,657 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:36,661 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:36,661 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:36,662 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_arg_syntax _______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:36,887 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:36,890 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:36,890 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:36,895 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:36,895 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:36,895 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_1 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:37,109 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:37,112 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:37,112 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:37,116 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:37,116 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:37,117 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_2 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:37,326 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:37,329 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:37,329 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:37,332 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:37,332 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:37,333 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_3 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:37,551 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:37,554 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:37,554 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:37,558 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:37,558 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:37,559 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_4 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:37,772 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:37,775 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:37,775 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:37,779 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:37,779 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:37,779 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______________________ ERROR at setup of test_templates_5 ______________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:38,000 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:38,003 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:38,003 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:38,007 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:38,007 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:38,007 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________ ERROR at setup of test_not_configured_url_returns_404 _____________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:38,255 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:38,258 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:38,258 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:38,262 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:38,262 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:38,263 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread __________________ ERROR at setup of test_webserver_plugin_ok __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:38,473 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:38,475 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:38,476 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:38,479 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:38,480 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:38,480 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _________________ ERROR at setup of test_trailing_no_slash_ok __________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:38,687 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:38,690 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:38,691 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:38,694 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:38,695 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:38,695 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ________________ ERROR at setup of test_trailing_slash_also_ok _________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:38,911 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:38,914 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:38,914 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:38,918 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:38,918 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:38,919 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _____________ ERROR at setup of test_json_is_automatically_decoded _____________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:39,133 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:39,136 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:39,136 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:39,140 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:39,140 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:39,141 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ______ ERROR at setup of test_json_on_custom_url_is_automatically_decoded ______ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:39,353 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:39,356 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:39,357 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:39,360 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:39,360 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:39,361 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _ ERROR at setup of test_post_form_on_webhook_without_form_param_is_automatically_decoded _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:39,582 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:39,585 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:39,585 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:39,589 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:39,589 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:39,590 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _ ERROR at setup of test_post_form_on_webhook_with_custom_url_and_without_form_param_is_automatically_decoded _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:39,798 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:39,801 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:39,801 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:39,805 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:39,805 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:39,806 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _ ERROR at setup of test_webhooks_with_form_parameter_decode_json_automatically _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:40,018 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:40,021 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:40,021 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:40,025 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:40,025 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:40,026 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _ ERROR at setup of test_webhooks_with_form_parameter_on_custom_url_decode_json_automatically _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:40,237 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:40,239 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:40,239 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:40,243 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:40,244 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:40,244 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______________ ERROR at setup of test_webhooks_with_raw_request _______________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:40,457 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:40,460 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:40,461 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:40,464 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:40,465 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:40,465 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______ ERROR at setup of test_webhooks_with_naked_decorator_raw_request _______ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:40,669 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:40,672 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:40,672 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:40,676 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:40,677 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:40,677 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread _______ ERROR at setup of test_generate_certificate_creates_usable_cert ________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:40,888 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:40,891 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:40,891 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:40,895 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:40,895 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:40,896 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________ ERROR at setup of test_custom_headers_and_status_codes ____________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:41,115 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:41,118 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:41,118 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:41,122 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:41,122 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:41,123 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ____________________ ERROR at setup of test_lambda_webhook _____________________ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: > bot = backendpm.load_plugin() ../../../errbot/bootstrap.py:186: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backend_plugin_manager.py:72: in load_plugin return clazz(self._config) ../../../errbot/backends/test.py:273: in __init__ super().__init__(config) ../../../errbot/core.py:53: in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) /usr/lib/python3.13/multiprocessing/pool.py:930: in __init__ Pool.__init__(self, processes, initializer, initargs) /usr/lib/python3.13/multiprocessing/pool.py:215: in __init__ self._repopulate_pool() /usr/lib/python3.13/multiprocessing/pool.py:306: in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, /usr/lib/python3.13/multiprocessing/pool.py:329: in _repopulate_pool_static w.start() /usr/lib/python3.13/multiprocessing/dummy/__init__.py:51: in start threading.Thread.start(self) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError During handling of the above exception, another exception occurred: request = > @pytest.fixture def testbot(request) -> TestBot: """ Pytest fixture to write tests against a fully functioning bot. For example, if you wanted to test the builtin `!about` command, you could write a test file with the following:: def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() It's possible to provide additional configuration to this fixture, by setting variables at module level or as class attributes (the latter taking precedence over the former). For example:: extra_plugin_dir = '/foo/bar' def test_about(testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..or:: extra_plugin_dir = '/foo/bar' class Tests: # Wins over `extra_plugin_dir = '/foo/bar'` above extra_plugin_dir = '/foo/baz' def test_about(self, testbot): testbot.push_message('!about') assert "Err version" in testbot.pop_message() ..to load additional plugins from the directory `/foo/bar` or `/foo/baz` respectively. This works for the following items, which are passed to the constructor of :class:`~errbot.backends.test.TestBot`: * `extra_plugin_dir` * `loglevel` """ def on_finish() -> TestBot: bot.stop() # setup the logging to something digestable. logger = logging.getLogger("") logging.getLogger("MARKDOWN").setLevel( logging.ERROR ) # this one is way too verbose in debug logger.setLevel(logging.DEBUG) console_hdlr = logging.StreamHandler(sys.stdout) console_hdlr.setFormatter( logging.Formatter("%(levelname)-8s %(name)-25s %(message)s") ) logger.handlers = [] logger.addHandler(console_hdlr) kwargs = {} for attr, default in ( ("extra_plugin_dir", None), ("extra_config", None), ("loglevel", logging.DEBUG), ): if hasattr(request, "instance"): kwargs[attr] = getattr(request.instance, attr, None) if kwargs[attr] is None: kwargs[attr] = getattr(request.module, attr, default) bot = TestBot(**kwargs) > bot.start() ../../../errbot/backends/test.py:705: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/backends/test.py:482: in start self._bot = setup_bot("Test", self.logger, self.bot_config) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ backend_name = 'Test', logger = config = restore = None def setup_bot( backend_name: str, logger: logging.Logger, config: object, restore: Optional[str] = None, ) -> ErrBot: # from here the environment is supposed to be set (daemon / non daemon, # config.py in the python path ) bot_config_defaults(config) if hasattr(config, "BOT_LOG_FORMATTER"): format_logs(formatter=config.BOT_LOG_FORMATTER) else: format_logs(theme_color=config.TEXT_COLOR_THEME) if hasattr(config, "BOT_LOG_FILE") and config.BOT_LOG_FILE: hdlr = logging.FileHandler(config.BOT_LOG_FILE) hdlr.setFormatter( logging.Formatter("%(asctime)s %(levelname)-8s %(name)-25s %(message)s") ) logger.addHandler(hdlr) if hasattr(config, "BOT_LOG_SENTRY") and config.BOT_LOG_SENTRY: sentry_integrations = [] try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk? " "(See https://docs.sentry.io/platforms/python for installation instructions)" ) exit(-1) sentry_logging = LoggingIntegration( level=config.SENTRY_LOGLEVEL, event_level=config.SENTRY_EVENTLEVEL ) sentry_integrations.append(sentry_logging) if hasattr(config, "BOT_LOG_SENTRY_FLASK") and config.BOT_LOG_SENTRY_FLASK: try: from sentry_sdk.integrations.flask import FlaskIntegration except ImportError: log.exception( "You have BOT_LOG_SENTRY enabled, but I couldn't import modules " "needed for Sentry integration. Did you install sentry-sdk[flask]? " "(See https://docs.sentry.io/platforms/python/flask for installation instructions)" ) exit(-1) sentry_integrations.append(FlaskIntegration()) sentry_options = getattr(config, "SENTRY_OPTIONS", {}) if hasattr(config, "SENTRY_TRANSPORT") and isinstance( config.SENTRY_TRANSPORT, tuple ): warnings.warn( "SENTRY_TRANSPORT is deprecated. Please use SENTRY_OPTIONS instead.", DeprecationWarning, ) try: mod = importlib.import_module(config.SENTRY_TRANSPORT[1]) transport = getattr(mod, config.SENTRY_TRANSPORT[0]) sentry_options["transport"] = transport except ImportError: log.exception( f"Unable to import selected SENTRY_TRANSPORT - {config.SENTRY_TRANSPORT}" ) exit(-1) # merge options dict with dedicated SENTRY_DSN setting sentry_kwargs = { **sentry_options, **{"dsn": config.SENTRY_DSN, "integrations": sentry_integrations}, } sentry_sdk.init(**sentry_kwargs) logger.setLevel(config.BOT_LOG_LEVEL) storage_plugin = get_storage_plugin(config) # init the botplugin manager botplugins_dir = path.join(config.BOT_DATA_DIR, PLUGINS_SUBDIR) if not path.exists(botplugins_dir): makedirs(botplugins_dir, mode=0o755) plugin_indexes = getattr(config, "BOT_PLUGIN_INDEXES", (PLUGIN_DEFAULT_INDEX,)) if isinstance(plugin_indexes, str): plugin_indexes = (plugin_indexes,) # Extra backend is expected to be a list type, convert string to list. extra_backend = getattr(config, "BOT_EXTRA_BACKEND_DIR", []) if isinstance(extra_backend, str): extra_backend = [extra_backend] backendpm = BackendPluginManager( config, "errbot.backends", backend_name, ErrBot, CORE_BACKENDS, extra_backend ) log.info(f"Found Backend plugin: {backendpm.plugin_info.name}") repo_manager = BotRepoManager(storage_plugin, botplugins_dir, plugin_indexes) try: bot = backendpm.load_plugin() botpm = BotPluginManager( storage_plugin, config.BOT_EXTRA_PLUGIN_DIR, config.AUTOINSTALL_DEPS, getattr(config, "CORE_PLUGINS", None), lambda name, clazz: clazz(bot, name), getattr(config, "PLUGINS_CALLBACK_ORDER", (None,)), ) bot.attach_storage_plugin(storage_plugin) bot.attach_repo_manager(repo_manager) bot.attach_plugin_manager(botpm) bot.initialize_backend_storage() # restore the bot from the restore script if restore: # Prepare the context for the restore script if "repos" in bot: log.fatal("You cannot restore onto a non empty bot.") sys.exit(-1) log.info(f"**** RESTORING the bot from {restore}") restore_bot_from_backup(restore, bot=bot, log=log) print("Restore complete. You can restart the bot normally") sys.exit(0) errors = bot.plugin_manager.update_plugin_places( repo_manager.get_all_repos_paths() ) if errors: startup_errors = "\n".join(errors.values()) log.error("Some plugins failed to load:\n%s", startup_errors) bot._plugin_errors_during_startup = startup_errors return bot except Exception: log.exception("Unable to load or configure the backend.") > exit(-1) E SystemExit: -1 ../../../errbot/bootstrap.py:221: SystemExit ---------------------------- Captured stdout setup ----------------------------- INFO errbot.bootstrap Found Storage plugin: Memory. INFO errbot.bootstrap Found Backend plugin: Test DEBUG errbot.storage Opening storage 'repomgr' DEBUG errbot.core ErrBot init. DEBUG errbot.backends.base Backend init. ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread ---------------------------- Captured stderr setup ----------------------------- 2025-02-01 18:49:41,337 INFO errbot.bootstrap Found Storage plugin: Memory. 2025-02-01 18:49:41,340 INFO errbot.bootstrap Found Backend plugin: Test 2025-02-01 18:49:41,340 DEBUG errbot.storage Opening storage 'repomgr' 2025-02-01 18:49:41,344 DEBUG errbot.core ErrBot init. 2025-02-01 18:49:41,345 DEBUG errbot.backends.base Backend init. 2025-02-01 18:49:41,345 ERROR errbot.bootstrap Unable to load or configure the backend. Traceback (most recent call last): File "/build/reproducible-path/errbot-6.2.0+ds/errbot/bootstrap.py", line 186, in setup_bot bot = backendpm.load_plugin() File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backend_plugin_manager.py", line 72, in load_plugin return clazz(self._config) File "/build/reproducible-path/errbot-6.2.0+ds/errbot/backends/test.py", line 273, in __init__ super().__init__(config) ~~~~~~~~~~~~~~~~^^^^^^^^ File "/build/reproducible-path/errbot-6.2.0+ds/errbot/core.py", line 53, in __init__ self.thread_pool = ThreadPool(bot_config.BOT_ASYNC_POOLSIZE) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 930, in __init__ Pool.__init__(self, processes, initializer, initargs) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 215, in __init__ self._repopulate_pool() ~~~~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 306, in _repopulate_pool return self._repopulate_pool_static(self._ctx, self.Process, ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ self._processes, ^^^^^^^^^^^^^^^^ ...<3 lines>... self._maxtasksperchild, ^^^^^^^^^^^^^^^^^^^^^^^ self._wrap_exception) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/multiprocessing/pool.py", line 329, in _repopulate_pool_static w.start() ~~~~~~~^^ File "/usr/lib/python3.13/multiprocessing/dummy/__init__.py", line 51, in start threading.Thread.start(self) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/usr/lib/python3.13/threading.py", line 973, in start _start_joinable_thread(self._bootstrap, handle=self._handle, ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ daemon=self.daemon) ^^^^^^^^^^^^^^^^^^^ RuntimeError: can't start new thread =================================== FAILURES =================================== ________________________________ test_streaming ________________________________ def test_streaming(): canary = b"this is my test" * 1000 source = Stream(TestPerson("gbin@gootz.net"), BytesIO(canary)) clients = [StreamingClient() for _ in range(50)] > Tee(source, clients).run() tests/streaming_test.py:17: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../errbot/streaming.py:81: in run thread.start() _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = def start(self): """Start the thread's activity. It must be called at most once per thread object. It arranges for the object's run() method to be invoked in a separate thread of control. This method will raise a RuntimeError if called more than once on the same thread object. """ if not self._initialized: raise RuntimeError("thread.__init__() not called") if self._started.is_set(): raise RuntimeError("threads can only be started once") with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread > _start_joinable_thread(self._bootstrap, handle=self._handle, daemon=self.daemon) E RuntimeError: can't start new thread /usr/lib/python3.13/threading.py:973: RuntimeError =============================== warnings summary =============================== ../../../errbot/utils.py:13 /build/reproducible-path/errbot-6.2.0+ds/errbot/utils.py:13: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html import pkg_resources -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html =========================== short test summary info ============================ FAILED tests/streaming_test.py::test_streaming - RuntimeError: can't start ne... ERROR tests/commands_test.py::test_echo - SystemExit: -1 ERROR tests/commands_test.py::test_status_gc - SystemExit: -1 ERROR tests/commands_test.py::test_config_cycle - SystemExit: -1 ERROR tests/commands_test.py::test_apropos - SystemExit: -1 ERROR tests/commands_test.py::test_logtail - SystemExit: -1 ERROR tests/commands_test.py::test_history - SystemExit: -1 ERROR tests/commands_test.py::test_encoding_preservation - SystemExit: -1 ERROR tests/commands_test.py::test_webserver_webhook_test - SystemExit: -1 ERROR tests/commands_test.py::test_activate_reload_and_deactivate - SystemExi... ERROR tests/commands_test.py::test_unblacklist_and_blacklist - SystemExit: -1 ERROR tests/commands_test.py::test_optional_prefix - SystemExit: -1 ERROR tests/commands_test.py::test_optional_prefix_re_cmd - SystemExit: -1 ERROR tests/commands_test.py::test_simple_match - SystemExit: -1 ERROR tests/commands_test.py::test_no_suggest_on_re_commands - SystemExit: -1 ERROR tests/commands_test.py::test_callback_no_command - SystemExit: -1 ERROR tests/commands_test.py::test_subcommands - SystemExit: -1 ERROR tests/commands_test.py::test_command_not_found_with_space_in_bot_prefix ERROR tests/commands_test.py::test_mock_injection - SystemExit: -1 ERROR tests/commands_test.py::test_multiline_command - SystemExit: -1 ERROR tests/commands_test.py::test_plugin_info_command - SystemExit: -1 ERROR tests/core_plugins_test.py::test_help_is_still_here - SystemExit: -1 ERROR tests/core_plugins_test.py::test_echo_still_here - SystemExit: -1 ERROR tests/core_plugins_test.py::test_bot_prefix_replaced - SystemExit: -1 ERROR tests/core_test.py::test_admins_to_notify - SystemExit: -1 ERROR tests/core_test.py::test_admins_not_notified - SystemExit: -1 ERROR tests/dependencies_test.py::test_if_all_loaded_by_default - SystemExit: -1 ERROR tests/dependencies_test.py::test_single_dependency - SystemExit: -1 ERROR tests/dependencies_test.py::test_double_dependency - SystemExit: -1 ERROR tests/dependencies_test.py::test_dependency_retrieval - SystemExit: -1 ERROR tests/dependencies_test.py::test_direct_circular_dependency - SystemExi... ERROR tests/dependencies_test.py::test_indirect_circular_dependency - SystemE... ERROR tests/dependencies_test.py::test_chained_dependency - SystemExit: -1 ERROR tests/dynaplug_test.py::test_simple - SystemExit: -1 ERROR tests/dynaplug_test.py::test_arg - SystemExit: -1 ERROR tests/dynaplug_test.py::test_re - SystemExit: -1 ERROR tests/dynaplug_test.py::test_saw - SystemExit: -1 ERROR tests/dynaplug_test.py::test_clashing - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_list_flows - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_no_autotrigger - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_autotrigger - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_no_duplicate_autotrigger - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_secondary_autotrigger - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_manual_flow - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_manual_flow_with_or_without_hinting - Syst... ERROR tests/flow_e2e_test.py::test_no_flyby_trigger_flow - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_flow_only - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_flow_only_help - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_flows_stop - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_flows_kill - SystemExit: -1 ERROR tests/flow_e2e_test.py::test_room_flow - SystemExit: -1 ERROR tests/i18n_test.py::test_i18n_return - SystemExit: -1 ERROR tests/i18n_test.py::test_i18n_simple_name - SystemExit: -1 ERROR tests/i18n_test.py::test_i18n_prefix - SystemExit: -1 ERROR tests/i18n_test.py::test_i18n_suffix - SystemExit: -1 ERROR tests/link_test.py::test_linked_plugin_here - SystemExit: -1 ERROR tests/matchall_test.py::test_botmatch_correct - SystemExit: -1 ERROR tests/matchall_test.py::test_botmatch - SystemExit: -1 ERROR tests/mention_test.py::test_foreign_mention - SystemExit: -1 ERROR tests/mention_test.py::test_testbot_mention - SystemExit: -1 ERROR tests/mention_test.py::test_multiple_mentions - SystemExit: -1 ERROR tests/muc_test.py::test_plugin_methods - SystemExit: -1 ERROR tests/muc_test.py::test_create_join_leave_destroy_lifecycle - SystemExi... ERROR tests/muc_test.py::test_occupants - SystemExit: -1 ERROR tests/muc_test.py::test_topic - SystemExit: -1 ERROR tests/muc_test.py::test_plugin_callbacks - SystemExit: -1 ERROR tests/muc_test.py::test_botcommands - SystemExit: -1 ERROR tests/multi_plugin_test.py::test_first - SystemExit: -1 ERROR tests/multi_plugin_test.py::test_second - SystemExit: -1 ERROR tests/plugin_config_fail_test.py::test_failed_config - SystemExit: -1 ERROR tests/plugin_config_test.py::test_failed_config - SystemExit: -1 ERROR tests/poller_test.py::test_delayed_hello - SystemExit: -1 ERROR tests/poller_test.py::test_delayed_hello_loop - SystemExit: -1 ERROR tests/syntax_test.py::test_nosyntax - SystemExit: -1 ERROR tests/syntax_test.py::test_syntax - SystemExit: -1 ERROR tests/syntax_test.py::test_re_syntax - SystemExit: -1 ERROR tests/syntax_test.py::test_arg_syntax - SystemExit: -1 ERROR tests/templates_test.py::test_templates_1 - SystemExit: -1 ERROR tests/templates_test.py::test_templates_2 - SystemExit: -1 ERROR tests/templates_test.py::test_templates_3 - SystemExit: -1 ERROR tests/templates_test.py::test_templates_4 - SystemExit: -1 ERROR tests/templates_test.py::test_templates_5 - SystemExit: -1 ERROR tests/webhooks_test.py::test_not_configured_url_returns_404 - SystemExi... ERROR tests/webhooks_test.py::test_webserver_plugin_ok - SystemExit: -1 ERROR tests/webhooks_test.py::test_trailing_no_slash_ok - SystemExit: -1 ERROR tests/webhooks_test.py::test_trailing_slash_also_ok - SystemExit: -1 ERROR tests/webhooks_test.py::test_json_is_automatically_decoded - SystemExit... ERROR tests/webhooks_test.py::test_json_on_custom_url_is_automatically_decoded ERROR tests/webhooks_test.py::test_post_form_on_webhook_without_form_param_is_automatically_decoded ERROR tests/webhooks_test.py::test_post_form_on_webhook_with_custom_url_and_without_form_param_is_automatically_decoded ERROR tests/webhooks_test.py::test_webhooks_with_form_parameter_decode_json_automatically ERROR tests/webhooks_test.py::test_webhooks_with_form_parameter_on_custom_url_decode_json_automatically ERROR tests/webhooks_test.py::test_webhooks_with_raw_request - SystemExit: -1 ERROR tests/webhooks_test.py::test_webhooks_with_naked_decorator_raw_request ERROR tests/webhooks_test.py::test_generate_certificate_creates_usable_cert ERROR tests/webhooks_test.py::test_custom_headers_and_status_codes - SystemEx... ERROR tests/webhooks_test.py::test_lambda_webhook - SystemExit: -1 ====== 1 failed, 110 passed, 8 deselected, 1 warning, 96 errors in 24.76s ====== E: pybuild pybuild:389: test: plugin distutils failed with: exit code=1: cd /build/reproducible-path/errbot-6.2.0+ds/.pybuild/cpython3_3.13_errbot/build; python3.13 -m pytest -k "not test_broken_plugin and not test_backup and not test_plugin_cycle and not test_entrypoint_paths and not test_check_dependencies_requirements_file_all_installed" rm -fr -- /tmp/dh-xdg-rundir-md2AX6pr dh_auto_test: error: pybuild --test --test-pytest -i python{version} -p "3.12 3.13" returned exit code 13 make[1]: *** [debian/rules:24: override_dh_auto_test] Error 25 make[1]: Leaving directory '/build/reproducible-path/errbot-6.2.0+ds' make: *** [debian/rules:17: binary] Error 2 dpkg-buildpackage: error: debian/rules binary subprocess returned exit status 2 I: copying local configuration E: Failed autobuilding of package I: unmounting dev/ptmx filesystem I: unmounting dev/pts filesystem I: unmounting dev/shm filesystem I: unmounting proc filesystem I: unmounting sys filesystem I: cleaning the build env I: removing directory /srv/workspace/pbuilder/41078 and its subdirectories Sun Feb 2 06:49:46 UTC 2025 W: No second build log, what happened?