2 Commits

Author SHA1 Message Date
0816232c1c Fix sync issue 2022-11-16 15:39:33 +01:00
3319920250 Check behavior of PRIVMSG when banned 2022-11-16 14:38:35 +01:00
38 changed files with 743 additions and 1528 deletions

View File

@ -3,12 +3,12 @@
jobs: jobs:
build-anope: build-anope:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-anope-devel key: 3-${{ runner.os }}-anope-devel
path: '~/.cache path: '~/.cache
@ -16,13 +16,13 @@ jobs:
${ github.workspace }/anope ${ github.workspace }/anope
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout Anope - name: Checkout Anope
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: anope path: anope
ref: 2.0.9 ref: 2.0.9
@ -37,18 +37,18 @@ jobs:
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-anope.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-anope.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-bahamut: build-bahamut:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-bahamut-devel key: 3-${{ runner.os }}-bahamut-devel
path: '~/.cache path: '~/.cache
@ -56,13 +56,13 @@ jobs:
${ github.workspace }/Bahamut ${ github.workspace }/Bahamut
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout Bahamut - name: Checkout Bahamut
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: Bahamut path: Bahamut
ref: master ref: master
@ -86,18 +86,18 @@ jobs:
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-bahamut.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-bahamut.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-bahamut name: installed-bahamut
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-hybrid: build-hybrid:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-hybrid-devel key: 3-${{ runner.os }}-hybrid-devel
path: '~/.cache path: '~/.cache
@ -105,13 +105,13 @@ jobs:
${ github.workspace }/ircd-hybrid ${ github.workspace }/ircd-hybrid
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout Hybrid - name: Checkout Hybrid
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: ircd-hybrid path: ircd-hybrid
ref: 8.2.x ref: 8.2.x
@ -125,23 +125,23 @@ jobs:
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-hybrid.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-hybrid.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-hybrid name: installed-hybrid
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-inspircd: build-inspircd:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout InspIRCd - name: Checkout InspIRCd
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: inspircd path: inspircd
ref: master ref: master
@ -149,29 +149,25 @@ jobs:
- name: Build InspIRCd - name: Build InspIRCd
run: | run: |
cd $GITHUB_WORKSPACE/inspircd/ cd $GITHUB_WORKSPACE/inspircd/
patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch
# Insp3 <= 3.16.0 and Insp4 <= 4.0.0a21 don't support -DINSPIRCD_UNLIMITED_MAINLOOP
patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch || true
./configure --prefix=$HOME/.local/inspircd --development ./configure --prefix=$HOME/.local/inspircd --development
make -j 4
CXXFLAGS=-DINSPIRCD_UNLIMITED_MAINLOOP make -j 4
make install make install
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-inspircd.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-inspircd.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-inspircd name: installed-inspircd
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-ngircd: build-ngircd:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-ngircd-devel key: 3-${{ runner.os }}-ngircd-devel
path: '~/.cache path: '~/.cache
@ -179,13 +175,13 @@ jobs:
${ github.workspace }/ngircd ${ github.workspace }/ngircd
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout ngircd - name: Checkout ngircd
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: ngircd path: ngircd
ref: master ref: master
@ -201,18 +197,18 @@ jobs:
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-ngircd.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-ngircd.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-ngircd name: installed-ngircd
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-plexus4: build-plexus4:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-plexus4-devel key: 3-${{ runner.os }}-plexus4-devel
path: '~/.cache path: '~/.cache
@ -220,9 +216,9 @@ jobs:
${ github.workspace }/placeholder ${ github.workspace }/placeholder
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: clone - name: clone
@ -243,18 +239,18 @@ jobs:
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-plexus4.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-plexus4.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-plexus4 name: installed-plexus4
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-solanum: build-solanum:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-solanum-devel key: 3-${{ runner.os }}-solanum-devel
path: '~/.cache path: '~/.cache
@ -262,13 +258,13 @@ jobs:
${ github.workspace }/solanum ${ github.workspace }/solanum
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout Solanum - name: Checkout Solanum
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: solanum path: solanum
ref: main ref: main
@ -283,18 +279,18 @@ jobs:
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-solanum.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-solanum.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-solanum name: installed-solanum
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-unrealircd: build-unrealircd:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-unrealircd-devel key: 3-${{ runner.os }}-unrealircd-devel
path: '~/.cache path: '~/.cache
@ -302,13 +298,13 @@ jobs:
${ github.workspace }/unrealircd ${ github.workspace }/unrealircd
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout UnrealIRCd 6 - name: Checkout UnrealIRCd 6
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: unrealircd path: unrealircd
ref: unreal60_dev ref: unreal60_dev
@ -324,24 +320,23 @@ jobs:
CFLAGS="-O0 -march=x86-64" CXXFLAGS="$CFLAGS" ./Config -quick CFLAGS="-O0 -march=x86-64" CXXFLAGS="$CFLAGS" ./Config -quick
make -j 4 make -j 4
make install make install
~/.local/unrealircd/unrealircd module install third/react
# Prevent download of geoIP database on first startup # Prevent download of geoIP database on first startup
sed -i 's/loadmodule "geoip_classic";//' ~/.local/unrealircd/conf/modules.default.conf sed -i 's/loadmodule "geoip_classic";//' ~/.local/unrealircd/conf/modules.default.conf
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-unrealircd.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-unrealircd.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-unrealircd name: installed-unrealircd
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-unrealircd-5: build-unrealircd-5:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-unrealircd-5-devel key: 3-${{ runner.os }}-unrealircd-5-devel
path: '~/.cache path: '~/.cache
@ -349,13 +344,13 @@ jobs:
${ github.workspace }/unrealircd ${ github.workspace }/unrealircd
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout UnrealIRCd 5 - name: Checkout UnrealIRCd 5
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: unrealircd path: unrealircd
ref: unreal52 ref: unreal52
@ -371,13 +366,12 @@ jobs:
CFLAGS="-O0 -march=x86-64" CXXFLAGS="$CFLAGS" ./Config -quick CFLAGS="-O0 -march=x86-64" CXXFLAGS="$CFLAGS" ./Config -quick
make -j 4 make -j 4
make install make install
~/.local/unrealircd/unrealircd module install third/react
# Prevent download of geoIP database on first startup # Prevent download of geoIP database on first startup
sed -i 's/loadmodule "geoip_classic";//' ~/.local/unrealircd/conf/modules.default.conf sed -i 's/loadmodule "geoip_classic";//' ~/.local/unrealircd/conf/modules.default.conf
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-unrealircd-5.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-unrealircd-5.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-unrealircd-5 name: installed-unrealircd-5
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
@ -407,11 +401,11 @@ jobs:
- test-unrealircd-anope - test-unrealircd-anope
- test-unrealircd-atheme - test-unrealircd-atheme
- test-unrealircd-dlk - test-unrealircd-dlk
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Download Artifacts - name: Download Artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
path: artifacts path: artifacts
- name: Install dashboard dependencies - name: Install dashboard dependencies
@ -434,15 +428,15 @@ jobs:
test-bahamut: test-bahamut:
needs: needs:
- build-bahamut - build-bahamut
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-bahamut name: installed-bahamut
path: '~' path: '~'
@ -460,7 +454,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_bahamut_devel name: pytest-results_bahamut_devel
path: pytest.xml path: pytest.xml
@ -468,20 +462,20 @@ jobs:
needs: needs:
- build-bahamut - build-bahamut
- build-anope - build-anope
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-bahamut name: installed-bahamut
path: '~' path: '~'
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: '~' path: '~'
@ -499,22 +493,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_bahamut-anope_devel name: pytest-results_bahamut-anope_devel
path: pytest.xml path: pytest.xml
test-bahamut-atheme: test-bahamut-atheme:
needs: needs:
- build-bahamut - build-bahamut
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-bahamut name: installed-bahamut
path: '~' path: '~'
@ -532,21 +526,21 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_bahamut-atheme_devel name: pytest-results_bahamut-atheme_devel
path: pytest.xml path: pytest.xml
test-ergo: test-ergo:
needs: [] needs: []
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout Ergo - name: Checkout Ergo
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: ergo path: ergo
ref: master ref: master
@ -572,7 +566,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_ergo_devel name: pytest-results_ergo_devel
path: pytest.xml path: pytest.xml
@ -580,20 +574,20 @@ jobs:
needs: needs:
- build-hybrid - build-hybrid
- build-anope - build-anope
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-hybrid name: installed-hybrid
path: '~' path: '~'
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: '~' path: '~'
@ -611,22 +605,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_hybrid_devel name: pytest-results_hybrid_devel
path: pytest.xml path: pytest.xml
test-inspircd: test-inspircd:
needs: needs:
- build-inspircd - build-inspircd
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-inspircd name: installed-inspircd
path: '~' path: '~'
@ -644,7 +638,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_inspircd_devel name: pytest-results_inspircd_devel
path: pytest.xml path: pytest.xml
@ -652,20 +646,20 @@ jobs:
needs: needs:
- build-inspircd - build-inspircd
- build-anope - build-anope
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-inspircd name: installed-inspircd
path: '~' path: '~'
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: '~' path: '~'
@ -683,21 +677,21 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_inspircd-anope_devel name: pytest-results_inspircd-anope_devel
path: pytest.xml path: pytest.xml
test-ircu2: test-ircu2:
needs: [] needs: []
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout ircu2 - name: Checkout ircu2
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: ircu2 path: ircu2
ref: u2_10_12_branch ref: u2_10_12_branch
@ -722,17 +716,17 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_ircu2_devel name: pytest-results_ircu2_devel
path: pytest.xml path: pytest.xml
test-limnoria: test-limnoria:
needs: [] needs: []
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Install dependencies - name: Install dependencies
@ -750,21 +744,21 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_limnoria_devel name: pytest-results_limnoria_devel
path: pytest.xml path: pytest.xml
test-nefarious: test-nefarious:
needs: [] needs: []
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout nefarious - name: Checkout nefarious
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: nefarious path: nefarious
ref: master ref: master
@ -788,22 +782,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_nefarious_devel name: pytest-results_nefarious_devel
path: pytest.xml path: pytest.xml
test-ngircd: test-ngircd:
needs: needs:
- build-ngircd - build-ngircd
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-ngircd name: installed-ngircd
path: '~' path: '~'
@ -821,7 +815,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_ngircd_devel name: pytest-results_ngircd_devel
path: pytest.xml path: pytest.xml
@ -829,20 +823,20 @@ jobs:
needs: needs:
- build-ngircd - build-ngircd
- build-anope - build-anope
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-ngircd name: installed-ngircd
path: '~' path: '~'
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: '~' path: '~'
@ -860,22 +854,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_ngircd-anope_devel name: pytest-results_ngircd-anope_devel
path: pytest.xml path: pytest.xml
test-ngircd-atheme: test-ngircd-atheme:
needs: needs:
- build-ngircd - build-ngircd
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-ngircd name: installed-ngircd
path: '~' path: '~'
@ -893,7 +887,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_ngircd-atheme_devel name: pytest-results_ngircd-atheme_devel
path: pytest.xml path: pytest.xml
@ -901,20 +895,20 @@ jobs:
needs: needs:
- build-plexus4 - build-plexus4
- build-anope - build-anope
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-plexus4 name: installed-plexus4
path: '~' path: '~'
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: '~' path: '~'
@ -932,22 +926,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_plexus4_devel name: pytest-results_plexus4_devel
path: pytest.xml path: pytest.xml
test-solanum: test-solanum:
needs: needs:
- build-solanum - build-solanum
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-solanum name: installed-solanum
path: '~' path: '~'
@ -965,17 +959,17 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_solanum_devel name: pytest-results_solanum_devel
path: pytest.xml path: pytest.xml
test-sopel: test-sopel:
needs: [] needs: []
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Install dependencies - name: Install dependencies
@ -992,22 +986,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_sopel_devel name: pytest-results_sopel_devel
path: pytest.xml path: pytest.xml
test-unrealircd: test-unrealircd:
needs: needs:
- build-unrealircd - build-unrealircd
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-unrealircd name: installed-unrealircd
path: '~' path: '~'
@ -1025,22 +1019,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_unrealircd_devel name: pytest-results_unrealircd_devel
path: pytest.xml path: pytest.xml
test-unrealircd-5: test-unrealircd-5:
needs: needs:
- build-unrealircd-5 - build-unrealircd-5
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-unrealircd-5 name: installed-unrealircd-5
path: '~' path: '~'
@ -1058,7 +1052,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_unrealircd-5_devel name: pytest-results_unrealircd-5_devel
path: pytest.xml path: pytest.xml
@ -1066,20 +1060,20 @@ jobs:
needs: needs:
- build-unrealircd - build-unrealircd
- build-anope - build-anope
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-unrealircd name: installed-unrealircd
path: '~' path: '~'
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: '~' path: '~'
@ -1097,22 +1091,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_unrealircd-anope_devel name: pytest-results_unrealircd-anope_devel
path: pytest.xml path: pytest.xml
test-unrealircd-atheme: test-unrealircd-atheme:
needs: needs:
- build-unrealircd - build-unrealircd
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-unrealircd name: installed-unrealircd
path: '~' path: '~'
@ -1130,29 +1124,29 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_unrealircd-atheme_devel name: pytest-results_unrealircd-atheme_devel
path: pytest.xml path: pytest.xml
test-unrealircd-dlk: test-unrealircd-dlk:
needs: needs:
- build-unrealircd - build-unrealircd
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-unrealircd name: installed-unrealircd
path: '~' path: '~'
- name: Unpack artefacts - name: Unpack artefacts
run: cd ~; find -name 'artefacts-*.tar.gz' -exec tar -xzf '{}' \; run: cd ~; find -name 'artefacts-*.tar.gz' -exec tar -xzf '{}' \;
- name: Checkout Dlk - name: Checkout Dlk
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: Dlk-Services path: Dlk-Services
ref: main ref: main
@ -1176,7 +1170,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_unrealircd-dlk_devel name: pytest-results_unrealircd-dlk_devel
path: pytest.xml path: pytest.xml

View File

@ -3,12 +3,12 @@
jobs: jobs:
build-anope: build-anope:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
key: 3-${{ runner.os }}-anope-devel_release key: 3-${{ runner.os }}-anope-devel_release
path: '~/.cache path: '~/.cache
@ -16,13 +16,13 @@ jobs:
${ github.workspace }/anope ${ github.workspace }/anope
' '
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout Anope - name: Checkout Anope
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: anope path: anope
ref: 2.0.9 ref: 2.0.9
@ -37,23 +37,23 @@ jobs:
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-anope.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-anope.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-inspircd: build-inspircd:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Checkout InspIRCd - name: Checkout InspIRCd
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
path: inspircd path: inspircd
ref: insp3 ref: insp3
@ -61,18 +61,14 @@ jobs:
- name: Build InspIRCd - name: Build InspIRCd
run: | run: |
cd $GITHUB_WORKSPACE/inspircd/ cd $GITHUB_WORKSPACE/inspircd/
patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch
# Insp3 <= 3.16.0 and Insp4 <= 4.0.0a21 don't support -DINSPIRCD_UNLIMITED_MAINLOOP
patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch || true
./configure --prefix=$HOME/.local/inspircd --development ./configure --prefix=$HOME/.local/inspircd --development
make -j 4
CXXFLAGS=-DINSPIRCD_UNLIMITED_MAINLOOP make -j 4
make install make install
- name: Make artefact tarball - name: Make artefact tarball
run: cd ~; tar -czf artefacts-inspircd.tar.gz .local/ go/ run: cd ~; tar -czf artefacts-inspircd.tar.gz .local/ go/
- name: Upload build artefacts - name: Upload build artefacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: installed-inspircd name: installed-inspircd
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
@ -84,11 +80,11 @@ jobs:
- test-inspircd - test-inspircd
- test-inspircd-anope - test-inspircd-anope
- test-inspircd-atheme - test-inspircd-atheme
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Download Artifacts - name: Download Artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
path: artifacts path: artifacts
- name: Install dashboard dependencies - name: Install dashboard dependencies
@ -111,15 +107,15 @@ jobs:
test-inspircd: test-inspircd:
needs: needs:
- build-inspircd - build-inspircd
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-inspircd name: installed-inspircd
path: '~' path: '~'
@ -137,7 +133,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_inspircd_devel_release name: pytest-results_inspircd_devel_release
path: pytest.xml path: pytest.xml
@ -145,20 +141,20 @@ jobs:
needs: needs:
- build-inspircd - build-inspircd
- build-anope - build-anope
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-inspircd name: installed-inspircd
path: '~' path: '~'
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-anope name: installed-anope
path: '~' path: '~'
@ -176,22 +172,22 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_inspircd-anope_devel_release name: pytest-results_inspircd-anope_devel_release
path: pytest.xml path: pytest.xml
test-inspircd-atheme: test-inspircd-atheme:
needs: needs:
- build-inspircd - build-inspircd
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.7 python-version: 3.7
- name: Download build artefacts - name: Download build artefacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v2
with: with:
name: installed-inspircd name: installed-inspircd
path: '~' path: '~'
@ -209,7 +205,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
- if: always() - if: always()
name: Publish results name: Publish results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: pytest-results_inspircd-atheme_devel_release name: pytest-results_inspircd-atheme_devel_release
path: pytest.xml path: pytest.xml

File diff suppressed because it is too large Load Diff

View File

@ -2,23 +2,22 @@ exclude: ^irctest/scram
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.1.0 rev: 22.3.0
hooks: hooks:
- id: black - id: black
language_version: python3 language_version: python3
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.11.5 rev: 5.5.2
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/PyCQA/flake8 - repo: https://gitlab.com/pycqa/flake8
rev: 5.0.4 rev: 5.0.4
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.0.1 rev: v0.812
hooks: hooks:
- id: mypy - id: mypy
additional_dependencies: [types-PyYAML, types-docutils]

View File

@ -110,11 +110,8 @@ cd /tmp/
git clone https://github.com/inspircd/inspircd.git git clone https://github.com/inspircd/inspircd.git
cd inspircd cd inspircd
# Optional, makes tests run considerably faster. Pick one depending on the InspIRCd version: # optional, makes tests run considerably faster
# on Insp3 <= 3.16.0 and Insp4 <= 4.0.0a21:
patch src/inspircd.cpp < ~/irctest/patches/inspircd_mainloop.patch patch src/inspircd.cpp < ~/irctest/patches/inspircd_mainloop.patch
# on Insp3 >= 3.17.0 and Insp4 >= 4.0.0a22:
export CXXFLAGS=-DINSPIRCD_UNLIMITED_MAINLOOP
./configure --prefix=$HOME/.local/ --development ./configure --prefix=$HOME/.local/ --development
make -j 4 make -j 4

View File

@ -1,27 +1,14 @@
from __future__ import annotations from __future__ import annotations
import dataclasses import dataclasses
import multiprocessing
import os import os
from pathlib import Path from pathlib import Path
import shutil import shutil
import socket import socket
import subprocess import subprocess
import tempfile import tempfile
import textwrap
import time import time
from typing import ( from typing import IO, Any, Callable, Dict, List, Optional, Set, Tuple, Type
IO,
Any,
Callable,
Dict,
List,
MutableMapping,
Optional,
Set,
Tuple,
Type,
)
import irctest import irctest
@ -70,43 +57,9 @@ class _BaseController:
supported_sasl_mechanisms: Set[str] supported_sasl_mechanisms: Set[str]
proc: Optional[subprocess.Popen] proc: Optional[subprocess.Popen]
_used_ports: Set[Tuple[str, int]]
"""``(hostname, port))`` used by this controller."""
# the following need to be shared between processes in case we are running in
# parallel (with pytest-xdist)
# The dicts are used as a set of (hostname, port), because _manager.set() doesn't
# exist.
_manager = multiprocessing.Manager()
_port_lock = _manager.Lock()
"""Lock for access to ``_all_used_ports`` and ``_available_ports``."""
_all_used_ports: MutableMapping[Tuple[str, int], None] = _manager.dict()
"""``(hostname, port)`` used by all controllers."""
_available_ports: MutableMapping[Tuple[str, int], None] = _manager.dict()
"""``(hostname, port)`` available to any controller."""
def __init__(self, test_config: TestCaseControllerConfig): def __init__(self, test_config: TestCaseControllerConfig):
self.test_config = test_config self.test_config = test_config
self.proc = None self.proc = None
self._used_ports = set()
def get_hostname_and_port(self) -> Tuple[str, int]:
with self._port_lock:
try:
# try to get a known available port
((hostname, port), _) = self._available_ports.popitem()
except KeyError:
# if there aren't any, iterate while we get a fresh one.
while True:
(hostname, port) = find_hostname_and_port()
if (hostname, port) not in self._all_used_ports:
# double-checking in self._used_ports to prevent collisions
# between controllers starting at the same time.
break
# Make this port unavailable to other processes
self._all_used_ports[(hostname, port)] = None
return (hostname, port)
def check_is_alive(self) -> None: def check_is_alive(self) -> None:
assert self.proc assert self.proc
@ -130,11 +83,6 @@ class _BaseController:
if self.proc: if self.proc:
self.kill_proc() self.kill_proc()
# move this controller's ports from _all_used_ports to _available_ports
for hostname, port in self._used_ports:
del self._all_used_ports[(hostname, port)]
self._available_ports[(hostname, port)] = None
class DirectoryBasedController(_BaseController): class DirectoryBasedController(_BaseController):
"""Helper for controllers whose software configuration is based on an """Helper for controllers whose software configuration is based on an
@ -208,18 +156,10 @@ class DirectoryBasedController(_BaseController):
], ],
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
) )
with self.dh_path.open("w") as fd: subprocess.check_output(
fd.write( [self.openssl_bin, "dhparam", "-out", self.dh_path, "128"],
textwrap.dedent( stderr=subprocess.DEVNULL,
""" )
-----BEGIN DH PARAMETERS-----
MIGHAoGBAJICSyQAiLj1fw8b5xELcnpqBQ+wvOyKgim4IetWOgZnRQFkTgOeoRZD
HksACRFJL/EqHxDKcy/2Ghwr2axhNxSJ+UOBmraP3WfodV/fCDPnZ+XnI9fjHsIr
rjisPMqomjXeiTB1UeAHvLUmCK4yx6lpAJsCYwJjsqkycUfHiy1bAgEC
-----END DH PARAMETERS-----
"""
)
)
class BaseClientController(_BaseController): class BaseClientController(_BaseController):
@ -253,6 +193,9 @@ class BaseServerController(_BaseController):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.faketime_enabled = False self.faketime_enabled = False
def get_hostname_and_port(self) -> Tuple[str, int]:
return find_hostname_and_port()
def run( def run(
self, self,
hostname: str, hostname: str,
@ -261,6 +204,8 @@ class BaseServerController(_BaseController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]],
invalid_metadata_keys: Optional[Set[str]],
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
raise NotImplementedError() raise NotImplementedError()

View File

@ -173,7 +173,7 @@ class _IrcTestCase(Generic[TController]):
) -> Optional[str]: ) -> Optional[str]:
"""Returns an error message if the message doesn't match the given arguments, """Returns an error message if the message doesn't match the given arguments,
or None if it matches.""" or None if it matches."""
for key, value in kwargs.items(): for (key, value) in kwargs.items():
if getattr(msg, key) != value: if getattr(msg, key) != value:
fail_msg = ( fail_msg = (
fail_msg or "expected {param} to be {expects}, got {got}: {msg}" fail_msg or "expected {param} to be {expects}, got {got}: {msg}"
@ -351,8 +351,8 @@ class BaseClientTestCase(_IrcTestCase[basecontrollers.BaseClientController]):
nick: Optional[str] = None nick: Optional[str] = None
user: Optional[List[str]] = None user: Optional[List[str]] = None
server: socket.socket server: socket.socket
protocol_version: Optional[str] protocol_version = Optional[str]
acked_capabilities: Optional[Set[str]] acked_capabilities = Optional[Set[str]]
__new__ = object.__new__ # pytest won't collect Generic[] subclasses otherwise __new__ = object.__new__ # pytest won't collect Generic[] subclasses otherwise
@ -448,9 +448,7 @@ class BaseClientTestCase(_IrcTestCase[basecontrollers.BaseClientController]):
print("{:.3f} S: {}".format(time.time(), line.strip())) print("{:.3f} S: {}".format(time.time(), line.strip()))
def readCapLs( def readCapLs(
self, self, auth: Optional[Authentication] = None, tls_config: tls.TlsConfig = None
auth: Optional[Authentication] = None,
tls_config: Optional[tls.TlsConfig] = None,
) -> None: ) -> None:
(hostname, port) = self.server.getsockname() (hostname, port) = self.server.getsockname()
self.controller.run( self.controller.run(
@ -460,9 +458,9 @@ class BaseClientTestCase(_IrcTestCase[basecontrollers.BaseClientController]):
m = self.getMessage() m = self.getMessage()
self.assertEqual(m.command, "CAP", "First message is not CAP LS.") self.assertEqual(m.command, "CAP", "First message is not CAP LS.")
if m.params == ["LS"]: if m.params == ["LS"]:
self.protocol_version = "301" self.protocol_version = 301
elif m.params == ["LS", "302"]: elif m.params == ["LS", "302"]:
self.protocol_version = "302" self.protocol_version = 302
elif m.params == ["END"]: elif m.params == ["END"]:
self.protocol_version = None self.protocol_version = None
else: else:
@ -529,6 +527,8 @@ class BaseServerTestCase(
password: Optional[str] = None password: Optional[str] = None
ssl = False ssl = False
valid_metadata_keys: Set[str] = set()
invalid_metadata_keys: Set[str] = set()
server_support: Optional[Dict[str, Optional[str]]] server_support: Optional[Dict[str, Optional[str]]]
run_services = False run_services = False
@ -548,6 +548,8 @@ class BaseServerTestCase(
self.hostname, self.hostname,
self.port, self.port,
password=self.password, password=self.password,
valid_metadata_keys=self.valid_metadata_keys,
invalid_metadata_keys=self.invalid_metadata_keys,
ssl=self.ssl, ssl=self.ssl,
run_services=self.run_services, run_services=self.run_services,
faketime=self.faketime, faketime=self.faketime,
@ -687,7 +689,7 @@ class BaseServerTestCase(
def connectClient( def connectClient(
self, self,
nick: str, nick: str,
name: Optional[TClientName] = None, name: TClientName = None,
capabilities: Optional[List[str]] = None, capabilities: Optional[List[str]] = None,
skip_if_cap_nak: bool = False, skip_if_cap_nak: bool = False,
show_io: Optional[bool] = None, show_io: Optional[bool] = None,
@ -706,7 +708,7 @@ class BaseServerTestCase(
self.requestCapabilities(client, capabilities, skip_if_cap_nak) self.requestCapabilities(client, capabilities, skip_if_cap_nak)
if password is not None: if password is not None:
if "sasl" not in (capabilities or ()): if "sasl" not in (capabilities or ()):
raise ValueError("Used 'password' option without sasl capbility") raise ValueError("Used 'password' option without sasl capbilitiy")
self.authenticateClient(client, account or nick, password) self.authenticateClient(client, account or nick, password)
self.sendLine(client, "NICK {}".format(nick)) self.sendLine(client, "NICK {}".format(nick))
@ -732,8 +734,8 @@ class BaseServerTestCase(
self.server_support[param] = None self.server_support[param] = None
welcome.append(m) welcome.append(m)
self.targmax: Dict[str, Optional[str]] = dict( # type: ignore[assignment] self.targmax: Dict[str, Optional[str]] = dict(
item.split(":", 1) item.split(":", 1) # type: ignore
for item in (self.server_support.get("TARGMAX") or "").split(",") for item in (self.server_support.get("TARGMAX") or "").split(",")
if item if item
) )

View File

@ -3,7 +3,12 @@ import shutil
import subprocess import subprocess
from typing import Optional, Set, Type from typing import Optional, Set, Type
from irctest.basecontrollers import BaseServerController, DirectoryBasedController from irctest.basecontrollers import (
BaseServerController,
DirectoryBasedController,
NotImplementedByController,
)
from irctest.irc_utils.junkdrawer import find_hostname_and_port
TEMPLATE_CONFIG = """ TEMPLATE_CONFIG = """
global {{ global {{
@ -107,14 +112,21 @@ class BahamutController(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
restricted_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
assert self.proc is None assert self.proc is None
self.port = port self.port = port
self.hostname = hostname self.hostname = hostname
self.create_config() self.create_config()
(unused_hostname, unused_port) = self.get_hostname_and_port() (unused_hostname, unused_port) = find_hostname_and_port()
(services_hostname, services_port) = self.get_hostname_and_port() (services_hostname, services_port) = find_hostname_and_port()
password_field = "passwd {};".format(password) if password else "" password_field = "passwd {};".format(password) if password else ""

View File

@ -1,8 +1,13 @@
import shutil import shutil
import subprocess import subprocess
from typing import Optional from typing import Optional, Set
from irctest.basecontrollers import BaseServerController, DirectoryBasedController from irctest.basecontrollers import (
BaseServerController,
DirectoryBasedController,
NotImplementedByController,
)
from irctest.irc_utils.junkdrawer import find_hostname_and_port
TEMPLATE_SSL_CONFIG = """ TEMPLATE_SSL_CONFIG = """
ssl_private_key = "{key_path}"; ssl_private_key = "{key_path}";
@ -36,13 +41,19 @@ class BaseHybridController(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
assert self.proc is None assert self.proc is None
self.port = port self.port = port
self.hostname = hostname self.hostname = hostname
self.create_config() self.create_config()
(services_hostname, services_port) = self.get_hostname_and_port() (services_hostname, services_port) = find_hostname_and_port()
password_field = 'password = "{}";'.format(password) if password else "" password_field = 'password = "{}";'.format(password) if password else ""
if ssl: if ssl:
self.gen_ssl() self.gen_ssl()

View File

@ -3,9 +3,13 @@ import json
import os import os
import shutil import shutil
import subprocess import subprocess
from typing import Any, Dict, Optional, Type, Union from typing import Any, Dict, Optional, Set, Type, Union
from irctest.basecontrollers import BaseServerController, DirectoryBasedController from irctest.basecontrollers import (
BaseServerController,
DirectoryBasedController,
NotImplementedByController,
)
from irctest.cases import BaseServerTestCase from irctest.cases import BaseServerTestCase
BASE_CONFIG = { BASE_CONFIG = {
@ -77,9 +81,6 @@ BASE_CONFIG = {
"channel-length": 128, "channel-length": 128,
"client-length": 128, "client-length": 128,
"chathistory-maxmessages": 100, "chathistory-maxmessages": 100,
"retention": {
"allow-individual-delete": True,
},
"tagmsg-storage": { "tagmsg-storage": {
"default": False, "default": False,
"whitelist": ["+draft/persist", "+persist"], "whitelist": ["+draft/persist", "+persist"],
@ -129,7 +130,7 @@ def hash_password(password: Union[str, bytes]) -> str:
["ergo", "genpasswd"], stdin=subprocess.PIPE, stdout=subprocess.PIPE ["ergo", "genpasswd"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
) )
out, _ = p.communicate(input_) out, _ = p.communicate(input_)
return out.decode("utf-8").strip() return out.decode("utf-8")
class ErgoController(BaseServerController, DirectoryBasedController): class ErgoController(BaseServerController, DirectoryBasedController):
@ -152,9 +153,17 @@ class ErgoController(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
restricted_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
config: Optional[Any] = None, config: Optional[Any] = None,
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
self.create_config() self.create_config()
if config is None: if config is None:
config = copy.deepcopy(BASE_CONFIG) config = copy.deepcopy(BASE_CONFIG)

View File

@ -1,5 +1,5 @@
import os import os
from typing import Optional, Tuple, Type from typing import Optional, Set, Tuple, Type
from irctest.basecontrollers import BaseServerController from irctest.basecontrollers import BaseServerController
@ -39,6 +39,9 @@ class ExternalServerController(BaseServerController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
restricted_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
pass pass

View File

@ -1,9 +1,13 @@
import functools
import shutil import shutil
import subprocess import subprocess
from typing import Optional, Type from typing import Optional, Set, Type
from irctest.basecontrollers import BaseServerController, DirectoryBasedController from irctest.basecontrollers import (
BaseServerController,
DirectoryBasedController,
NotImplementedByController,
)
from irctest.irc_utils.junkdrawer import find_hostname_and_port
TEMPLATE_CONFIG = """ TEMPLATE_CONFIG = """
# Clients: # Clients:
@ -76,8 +80,8 @@ TEMPLATE_CONFIG = """
# HELP/HELPOP # HELP/HELPOP
<module name="alias"> # for the HELP alias <module name="alias"> # for the HELP alias
<module name="{help_module_name}"> <module name="helpop">
<include file="examples/{help_module_name}.conf.example"> <include file="examples/helpop.conf.example">
# Misc: # Misc:
<log method="file" type="*" level="debug" target="/tmp/ircd-{port}.log"> <log method="file" type="*" level="debug" target="/tmp/ircd-{port}.log">
@ -90,17 +94,6 @@ TEMPLATE_SSL_CONFIG = """
""" """
@functools.lru_cache()
def installed_version() -> int:
output = subprocess.check_output(["inspircd", "--version"], universal_newlines=True)
if output.startswith("InspIRCd-3"):
return 3
if output.startswith("InspIRCd-4"):
return 4
else:
assert False, f"unexpected version: {output}"
class InspircdController(BaseServerController, DirectoryBasedController): class InspircdController(BaseServerController, DirectoryBasedController):
software_name = "InspIRCd" software_name = "InspIRCd"
supported_sasl_mechanisms = {"PLAIN"} supported_sasl_mechanisms = {"PLAIN"}
@ -120,13 +113,20 @@ class InspircdController(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
restricted_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str] = None, faketime: Optional[str] = None,
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
assert self.proc is None assert self.proc is None
self.port = port self.port = port
self.hostname = hostname self.hostname = hostname
self.create_config() self.create_config()
(services_hostname, services_port) = self.get_hostname_and_port() (services_hostname, services_port) = find_hostname_and_port()
password_field = 'password="{}"'.format(password) if password else "" password_field = 'password="{}"'.format(password) if password else ""
@ -138,13 +138,6 @@ class InspircdController(BaseServerController, DirectoryBasedController):
else: else:
ssl_config = "" ssl_config = ""
if installed_version() == 3:
help_module_name = "helpop"
elif installed_version() == 4:
help_module_name = "help"
else:
assert False, f"unexpected version: {installed_version()}"
with self.open_file("server.conf") as fd: with self.open_file("server.conf") as fd:
fd.write( fd.write(
TEMPLATE_CONFIG.format( TEMPLATE_CONFIG.format(
@ -154,7 +147,6 @@ class InspircdController(BaseServerController, DirectoryBasedController):
services_port=services_port, services_port=services_port,
password_field=password_field, password_field=password_field,
ssl_config=ssl_config, ssl_config=ssl_config,
help_module_name=help_module_name,
) )
) )
assert self.directory assert self.directory

View File

@ -1,6 +1,6 @@
import shutil import shutil
import subprocess import subprocess
from typing import Optional, Type from typing import Optional, Set, Type
from irctest.basecontrollers import ( from irctest.basecontrollers import (
BaseServerController, BaseServerController,
@ -49,8 +49,14 @@ class Irc2Controller(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
if ssl: if ssl:
raise NotImplementedByController("TLS") raise NotImplementedByController("TLS")
if run_services: if run_services:

View File

@ -1,6 +1,6 @@
import shutil import shutil
import subprocess import subprocess
from typing import Optional, Type from typing import Optional, Set, Type
from irctest.basecontrollers import ( from irctest.basecontrollers import (
BaseServerController, BaseServerController,
@ -68,8 +68,14 @@ class Ircu2Controller(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
if ssl: if ssl:
raise NotImplementedByController("TLS") raise NotImplementedByController("TLS")
if run_services: if run_services:

View File

@ -33,10 +33,10 @@ extensions:
- mammon.ext.ircv3.sasl - mammon.ext.ircv3.sasl
- mammon.ext.misc.nopost - mammon.ext.misc.nopost
metadata: metadata:
restricted_keys: [] restricted_keys:
{restricted_keys}
whitelist: whitelist:
- display-name {authorized_keys}
- avatar
monitor: monitor:
limit: 20 limit: 20
motd: motd:
@ -89,6 +89,9 @@ class MammonController(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
restricted_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
if password is not None: if password is not None:
@ -104,6 +107,8 @@ class MammonController(BaseServerController, DirectoryBasedController):
directory=self.directory, directory=self.directory,
hostname=hostname, hostname=hostname,
port=port, port=port,
authorized_keys=make_list(valid_metadata_keys or set()),
restricted_keys=make_list(restricted_metadata_keys or set()),
) )
) )
# with self.open_file('server.yml', 'r') as fd: # with self.open_file('server.yml', 'r') as fd:

View File

@ -2,7 +2,12 @@ import shutil
import subprocess import subprocess
from typing import Optional, Set, Type from typing import Optional, Set, Type
from irctest.basecontrollers import BaseServerController, DirectoryBasedController from irctest.basecontrollers import (
BaseServerController,
DirectoryBasedController,
NotImplementedByController,
)
from irctest.irc_utils.junkdrawer import find_hostname_and_port
TEMPLATE_CONFIG = """ TEMPLATE_CONFIG = """
[Global] [Global]
@ -48,13 +53,20 @@ class NgircdController(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
restricted_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
assert self.proc is None assert self.proc is None
self.port = port self.port = port
self.hostname = hostname self.hostname = hostname
self.create_config() self.create_config()
(unused_hostname, unused_port) = self.get_hostname_and_port() (unused_hostname, unused_port) = find_hostname_and_port()
password_field = "Password = {}".format(password) if password else "" password_field = "Password = {}".format(password) if password else ""

View File

@ -1,6 +1,6 @@
import shutil import shutil
import subprocess import subprocess
from typing import Optional, Type from typing import Optional, Set, Type
from irctest.basecontrollers import ( from irctest.basecontrollers import (
BaseServerController, BaseServerController,
@ -67,8 +67,14 @@ class SnircdController(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
if ssl: if ssl:
raise NotImplementedByController("TLS") raise NotImplementedByController("TLS")
if run_services: if run_services:

View File

@ -73,7 +73,7 @@ class SopelController(BaseClientController):
auth_method="auth_method = sasl" if auth else "", auth_method="auth_method = sasl" if auth else "",
) )
) )
self.proc = subprocess.Popen(["sopel", "-c", self.filename]) self.proc = subprocess.Popen(["sopel", "--quiet", "-c", self.filename])
def get_irctest_controller_class() -> Type[SopelController]: def get_irctest_controller_class() -> Type[SopelController]:

View File

@ -5,15 +5,19 @@ from pathlib import Path
import shutil import shutil
import subprocess import subprocess
import textwrap import textwrap
from typing import Callable, ContextManager, Iterator, Optional, Type from typing import Callable, ContextManager, Iterator, Optional, Set, Type
from irctest.basecontrollers import BaseServerController, DirectoryBasedController from irctest.basecontrollers import (
BaseServerController,
DirectoryBasedController,
NotImplementedByController,
)
from irctest.irc_utils.junkdrawer import find_hostname_and_port
TEMPLATE_CONFIG = """ TEMPLATE_CONFIG = """
include "modules.default.conf"; include "modules.default.conf";
include "operclass.default.conf"; include "operclass.default.conf";
{extras} {extras}
loadmodule "third/redact";
include "help/help.conf"; include "help/help.conf";
me {{ me {{
@ -97,12 +101,7 @@ set {{
}} }}
modes-on-join "+H 100:1d"; // Enables CHATHISTORY modes-on-join "+H 100:1d"; // Enables CHATHISTORY
redacters {{ {set_extras}
op;
sender;
}}
{set_v6only}
}} }}
@ -125,24 +124,6 @@ oper "operuser" {{
}} }}
""" """
SET_V6ONLY = """
// Remove RPL_WHOISSPECIAL used to advertise security groups
whois-details {
security-groups { everyone none; self none; oper none; }
}
plaintext-policy {
server warn; // https://www.unrealircd.org/docs/FAQ#server-requires-tls
oper warn; // https://www.unrealircd.org/docs/FAQ#oper-requires-tls
}
anti-flood {
everyone {
connect-flood 255:10;
}
}
"""
def _filelock(path: Path) -> Callable[[], ContextManager]: def _filelock(path: Path) -> Callable[[], ContextManager]:
"""Alternative to :cls:`multiprocessing.Lock` that works with pytest-xdist""" """Alternative to :cls:`multiprocessing.Lock` that works with pytest-xdist"""
@ -205,8 +186,15 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
password: Optional[str], password: Optional[str],
ssl: bool, ssl: bool,
run_services: bool, run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
restricted_metadata_keys: Optional[Set[str]] = None,
faketime: Optional[str], faketime: Optional[str],
) -> None: ) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
assert self.proc is None assert self.proc is None
self.port = port self.port = port
self.hostname = hostname self.hostname = hostname
@ -219,54 +207,64 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
loadmodule "cloak_md5"; loadmodule "cloak_md5";
""" """
) )
set_v6only = SET_V6ONLY set_extras = textwrap.indent(
textwrap.dedent(
"""
// Remove RPL_WHOISSPECIAL used to advertise security groups
whois-details {
security-groups { everyone none; self none; oper none; }
}
"""
),
" ",
)
else: else:
extras = "" extras = ""
set_v6only = "" set_extras = ""
with self.open_file("empty.txt") as fd: with self.open_file("empty.txt") as fd:
fd.write("\n") fd.write("\n")
password_field = 'password "{}";'.format(password) if password else "" password_field = 'password "{}";'.format(password) if password else ""
(services_hostname, services_port) = self.get_hostname_and_port()
(unused_hostname, unused_port) = self.get_hostname_and_port()
self.gen_ssl()
if ssl:
(tls_hostname, tls_port) = (hostname, port)
(hostname, port) = (unused_hostname, unused_port)
else:
# Unreal refuses to start without TLS enabled
(tls_hostname, tls_port) = (unused_hostname, unused_port)
assert self.directory
with self.open_file("unrealircd.conf") as fd:
fd.write(
TEMPLATE_CONFIG.format(
hostname=hostname,
port=port,
services_hostname=services_hostname,
services_port=services_port,
tls_hostname=tls_hostname,
tls_port=tls_port,
password_field=password_field,
key_path=self.key_path,
pem_path=self.pem_path,
empty_file=self.directory / "empty.txt",
set_v6only=set_v6only,
extras=extras,
)
)
if faketime and shutil.which("faketime"):
faketime_cmd = ["faketime", "-f", faketime]
self.faketime_enabled = True
else:
faketime_cmd = []
with _STARTSTOP_LOCK(): with _STARTSTOP_LOCK():
(services_hostname, services_port) = find_hostname_and_port()
(unused_hostname, unused_port) = find_hostname_and_port()
self.gen_ssl()
if ssl:
(tls_hostname, tls_port) = (hostname, port)
(hostname, port) = (unused_hostname, unused_port)
else:
# Unreal refuses to start without TLS enabled
(tls_hostname, tls_port) = (unused_hostname, unused_port)
assert self.directory
with self.open_file("unrealircd.conf") as fd:
fd.write(
TEMPLATE_CONFIG.format(
hostname=hostname,
port=port,
services_hostname=services_hostname,
services_port=services_port,
tls_hostname=tls_hostname,
tls_port=tls_port,
password_field=password_field,
key_path=self.key_path,
pem_path=self.pem_path,
empty_file=self.directory / "empty.txt",
extras=extras,
set_extras=set_extras,
)
)
if faketime and shutil.which("faketime"):
faketime_cmd = ["faketime", "-f", faketime]
self.faketime_enabled = True
else:
faketime_cmd = []
self.proc = subprocess.Popen( self.proc = subprocess.Popen(
[ [
*faketime_cmd, *faketime_cmd,

View File

@ -39,7 +39,7 @@ class CaseResult:
type: Optional[str] = None type: Optional[str] = None
message: Optional[str] = None message: Optional[str] = None
def output_filename(self) -> str: def output_filename(self):
test_name = self.test_name test_name = self.test_name
if len(test_name) > 50 or set(test_name) & NETLIFY_CHAR_BLACKLIST: if len(test_name) > 50 or set(test_name) & NETLIFY_CHAR_BLACKLIST:
# File name too long or otherwise invalid. This should be good enough: # File name too long or otherwise invalid. This should be good enough:
@ -75,7 +75,7 @@ def iter_job_results(job_file_name: Path, job: ET.ElementTree) -> Iterator[CaseR
skipped = False skipped = False
details = None details = None
system_out = None system_out = None
extra: Dict[str, str] = {} extra = {}
for child in case: for child in case:
if child.tag == "skipped": if child.tag == "skipped":
success = True success = True
@ -187,7 +187,7 @@ def build_test_table(jobs: List[str], results: List[CaseResult]) -> ET.Element:
ET.SubElement(ET.SubElement(cell, "div"), "span").text = job ET.SubElement(ET.SubElement(cell, "div"), "span").text = job
cell.set("class", "job-name") cell.set("class", "job-name")
for (module_name, class_name), class_results in sorted( for ((module_name, class_name), class_results) in sorted(
results_by_module_and_class.items() results_by_module_and_class.items()
): ):
if multiple_modules: if multiple_modules:
@ -220,7 +220,7 @@ def build_test_table(jobs: List[str], results: List[CaseResult]) -> ET.Element:
# One row for each test: # One row for each test:
results_by_test = group_by(class_results, key=lambda r: r.test_name) results_by_test = group_by(class_results, key=lambda r: r.test_name)
for test_name, test_results in sorted(results_by_test.items()): for (test_name, test_results) in sorted(results_by_test.items()):
row_anchor = f"{qualified_class_name}.{test_name}" row_anchor = f"{qualified_class_name}.{test_name}"
if len(row_anchor) >= 50: if len(row_anchor) >= 50:
# Too long; give up on generating readable URL # Too long; give up on generating readable URL
@ -314,7 +314,7 @@ def write_html_pages(
pages = [] pages = []
for module_name, module_results in sorted(results_by_module.items()): for (module_name, module_results) in sorted(results_by_module.items()):
# Filter out client jobs if this is a server test module, and vice versa # Filter out client jobs if this is a server test module, and vice versa
module_categories = { module_categories = {
job_categories[result.job] job_categories[result.job]
@ -366,7 +366,7 @@ def write_html_index(output_dir: Path, pages: List[Tuple[str, str, str]]) -> Non
module_pages = [] module_pages = []
job_pages = [] job_pages = []
for page_type, title, file_name in sorted(pages): for (page_type, title, file_name) in sorted(pages):
if page_type == "module": if page_type == "module":
module_pages.append((title, file_name)) module_pages.append((title, file_name))
elif page_type == "job": elif page_type == "job":
@ -379,7 +379,7 @@ def write_html_index(output_dir: Path, pages: List[Tuple[str, str, str]]) -> Non
dl = ET.SubElement(body, "dl") dl = ET.SubElement(body, "dl")
dl.set("class", "module-index") dl.set("class", "module-index")
for module_name, file_name in sorted(module_pages): for (module_name, file_name) in sorted(module_pages):
module = importlib.import_module(module_name) module = importlib.import_module(module_name)
link = ET.SubElement(ET.SubElement(dl, "dt"), "a", href=f"./{file_name}") link = ET.SubElement(ET.SubElement(dl, "dt"), "a", href=f"./{file_name}")
@ -391,7 +391,7 @@ def write_html_index(output_dir: Path, pages: List[Tuple[str, str, str]]) -> Non
ul = ET.SubElement(body, "ul") ul = ET.SubElement(body, "ul")
ul.set("class", "job-index") ul.set("class", "job-index")
for job, file_name in sorted(job_pages): for (job, file_name) in sorted(job_pages):
link = ET.SubElement(ET.SubElement(ul, "li"), "a", href=f"./{file_name}") link = ET.SubElement(ET.SubElement(ul, "li"), "a", href=f"./{file_name}")
link.text = job link.text = job

View File

@ -18,7 +18,7 @@ class Artifact:
download_url: str download_url: str
@property @property
def public_download_url(self) -> str: def public_download_url(self):
# GitHub API is not available publicly for artifacts, we need to use # GitHub API is not available publicly for artifacts, we need to use
# a third-party proxy to access it... # a third-party proxy to access it...
name = urllib.parse.quote(self.name) name = urllib.parse.quote(self.name)

View File

@ -152,7 +152,7 @@ def match_dict(
# Set to not-None if we find a Keys() operator in the dict keys # Set to not-None if we find a Keys() operator in the dict keys
remaining_keys_wildcard = None remaining_keys_wildcard = None
for expected_key, expected_value in expected.items(): for (expected_key, expected_value) in expected.items():
if isinstance(expected_key, RemainingKeys): if isinstance(expected_key, RemainingKeys):
remaining_keys_wildcard = (expected_key.key, expected_value) remaining_keys_wildcard = (expected_key.key, expected_value)
else: else:
@ -168,7 +168,7 @@ def match_dict(
if remaining_keys_wildcard: if remaining_keys_wildcard:
(expected_key, expected_value) = remaining_keys_wildcard (expected_key, expected_value) = remaining_keys_wildcard
for key, value in got.items(): for (key, value) in got.items():
if not match_string(key, expected_key): if not match_string(key, expected_key):
return False return False
if not match_string(value, expected_value): if not match_string(value, expected_value):

View File

@ -9,7 +9,6 @@ from irctest.patma import ANYSTR
REGISTER_CAP_NAME = "draft/account-registration" REGISTER_CAP_NAME = "draft/account-registration"
@cases.mark_services
@cases.mark_specifications("IRCv3") @cases.mark_specifications("IRCv3")
class RegisterBeforeConnectTestCase(cases.BaseServerTestCase): class RegisterBeforeConnectTestCase(cases.BaseServerTestCase):
@staticmethod @staticmethod
@ -34,7 +33,6 @@ class RegisterBeforeConnectTestCase(cases.BaseServerTestCase):
self.assertMessageMatch(register_response, params=["SUCCESS", ANYSTR, ANYSTR]) self.assertMessageMatch(register_response, params=["SUCCESS", ANYSTR, ANYSTR])
@cases.mark_services
@cases.mark_specifications("IRCv3") @cases.mark_specifications("IRCv3")
class RegisterBeforeConnectDisallowedTestCase(cases.BaseServerTestCase): class RegisterBeforeConnectDisallowedTestCase(cases.BaseServerTestCase):
@staticmethod @staticmethod
@ -62,7 +60,6 @@ class RegisterBeforeConnectDisallowedTestCase(cases.BaseServerTestCase):
) )
@cases.mark_services
@cases.mark_specifications("IRCv3") @cases.mark_specifications("IRCv3")
class RegisterEmailVerifiedTestCase(cases.BaseServerTestCase): class RegisterEmailVerifiedTestCase(cases.BaseServerTestCase):
@staticmethod @staticmethod
@ -113,7 +110,6 @@ class RegisterEmailVerifiedTestCase(cases.BaseServerTestCase):
) )
@cases.mark_services
@cases.mark_specifications("IRCv3", "Ergo") @cases.mark_specifications("IRCv3", "Ergo")
class RegisterNoLandGrabsTestCase(cases.BaseServerTestCase): class RegisterNoLandGrabsTestCase(cases.BaseServerTestCase):
@staticmethod @staticmethod

View File

@ -7,14 +7,18 @@ and ban exception (`Modern <https://modern.ircdocs.horse/#exception-channel-mode
""" """
from irctest import cases, runner from irctest import cases, runner
from irctest.numerics import ERR_BANNEDFROMCHAN, RPL_BANLIST, RPL_ENDOFBANLIST from irctest.numerics import (
ERR_BANNEDFROMCHAN,
ERR_CANNOTSENDTOCHAN,
RPL_BANLIST,
RPL_ENDOFBANLIST,
)
from irctest.patma import ANYSTR, StrRe from irctest.patma import ANYSTR, StrRe
class BanModeTestCase(cases.BaseServerTestCase): class BanModeTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("RFC1459", "RFC2812", "Modern") @cases.mark_specifications("RFC1459", "RFC2812", "Modern")
def testBan(self): def testBanJoin(self):
"""Basic ban operation"""
self.connectClient("chanop", name="chanop") self.connectClient("chanop", name="chanop")
self.joinChannel("chanop", "#chan") self.joinChannel("chanop", "#chan")
self.getMessages("chanop") self.getMessages("chanop")
@ -32,6 +36,55 @@ class BanModeTestCase(cases.BaseServerTestCase):
self.sendLine("bar", "JOIN #chan") self.sendLine("bar", "JOIN #chan")
self.assertMessageMatch(self.getMessage("bar"), command="JOIN") self.assertMessageMatch(self.getMessage("bar"), command="JOIN")
@cases.mark_specifications("Modern")
def testBanPrivmsg(self):
"""
TODO: this checks the following quote is false:
"If `<target>` is a channel name and the client is [banned](#ban-channel-mode)
and not covered by a [ban exception](#ban-exception-channel-mode), the
message will not be delivered and the command will silently fail."
-- https://modern.ircdocs.horse/#privmsg-message
to check https://github.com/ircdocs/modern-irc/pull/201
"""
self.connectClient("chanop", name="chanop")
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.connectClient("Bar", name="bar")
self.getMessages("bar")
self.sendLine("bar", "JOIN #chan")
self.getMessages("bar")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b bar!*@*")
self.assertMessageMatch(self.getMessage("chanop"), command="MODE")
self.getMessages("chanop")
self.getMessages("bar")
self.sendLine("bar", "PRIVMSG #chan :hello world")
self.assertMessageMatch(
self.getMessage("bar"),
command=ERR_CANNOTSENDTOCHAN,
params=["Bar", "#chan", ANYSTR],
)
self.assertEqual(self.getMessages("bar"), [])
self.assertEqual(self.getMessages("chanop"), [])
self.sendLine("chanop", "MODE #chan -b bar!*@*")
self.assertMessageMatch(self.getMessage("chanop"), command="MODE")
self.getMessages("chanop")
self.getMessages("bar")
self.sendLine("bar", "PRIVMSG #chan :hello again")
self.assertEqual(self.getMessages("bar"), [])
self.assertMessageMatch(
self.getMessage("chanop"),
command="PRIVMSG",
params=["#chan", "hello again"],
)
@cases.mark_specifications("Modern") @cases.mark_specifications("Modern")
def testBanList(self): def testBanList(self):
"""`RPL_BANLIST <https://modern.ircdocs.horse/#rplbanlist-367>`_""" """`RPL_BANLIST <https://modern.ircdocs.horse/#rplbanlist-367>`_"""

View File

@ -1,38 +0,0 @@
"""
Channel "no external messages" mode (`RFC 1459
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.3.1>`__,
`Modern <https://modern.ircdocs.horse/#no-external-messages-mode>`__)
"""
from irctest import cases
from irctest.numerics import ERR_CANNOTSENDTOCHAN
class NoExternalMessagesTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("RFC1459", "Modern")
def testNoExternalMessagesMode(self):
# test the +n channel mode
self.connectClient("chanop", name="chanop")
self.joinChannel("chanop", "#chan")
self.sendLine("chanop", "MODE #chan +n")
self.getMessages("chanop")
self.connectClient("baz", name="baz")
# this message should be suppressed completely by +n
self.sendLine("baz", "PRIVMSG #chan :hi from baz")
replies = self.getMessages("baz")
reply_cmds = {reply.command for reply in replies}
self.assertIn(ERR_CANNOTSENDTOCHAN, reply_cmds)
self.assertEqual(self.getMessages("chanop"), [])
# set the channel to -n: baz should be able to send now
self.sendLine("chanop", "MODE #chan -n")
replies = self.getMessages("chanop")
modeLines = [line for line in replies if line.command == "MODE"]
self.assertMessageMatch(modeLines[0], command="MODE", params=["#chan", "-n"])
self.sendLine("baz", "PRIVMSG #chan :hi again from baz")
self.getMessages("baz")
relays = self.getMessages("chanop")
self.assertMessageMatch(
relays[0], command="PRIVMSG", params=["#chan", "hi again from baz"]
)

View File

@ -32,26 +32,6 @@ class PrivmsgTestCase(cases.BaseServerTestCase):
# ERR_NOSUCHNICK, ERR_NOSUCHCHANNEL, or ERR_CANNOTSENDTOCHAN # ERR_NOSUCHNICK, ERR_NOSUCHCHANNEL, or ERR_CANNOTSENDTOCHAN
self.assertIn(msg.command, ("401", "403", "404")) self.assertIn(msg.command, ("401", "403", "404"))
@cases.mark_specifications("RFC1459", "RFC2812")
def testPrivmsgToUser(self):
"""<https://tools.ietf.org/html/rfc2812#section-3.3.1>"""
self.connectClient("foo")
self.connectClient("bar")
self.sendLine(1, "PRIVMSG bar :hey there!")
self.getMessages(1)
pms = [msg for msg in self.getMessages(2) if msg.command == "PRIVMSG"]
self.assertEqual(len(pms), 1)
self.assertMessageMatch(pms[0], command="PRIVMSG", params=["bar", "hey there!"])
@cases.mark_specifications("RFC1459", "RFC2812")
def testPrivmsgNonexistentUser(self):
"""https://tools.ietf.org/html/rfc2812#section-3.3.1"""
self.connectClient("foo")
self.sendLine(1, "PRIVMSG bar :hey there!")
msg = self.getMessage(1)
# ERR_NOSUCHNICK
self.assertIn(msg.command, ("401"))
class NoticeTestCase(cases.BaseServerTestCase): class NoticeTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("RFC1459", "RFC2812") @cases.mark_specifications("RFC1459", "RFC2812")
@ -100,13 +80,8 @@ class NoticeTestCase(cases.BaseServerTestCase):
class TagsTestCase(cases.BaseServerTestCase): class TagsTestCase(cases.BaseServerTestCase):
@cases.mark_capabilities("message-tags") @cases.mark_capabilities("message-tags")
@cases.xfailIf( @cases.xfailIfSoftware(
lambda self: bool( ["UnrealIRCd"], "https://bugs.unrealircd.org/view.php?id=5947"
self.controller.software_name == "UnrealIRCd"
and self.controller.software_version == 5
),
"UnrealIRCd <6.0.7 dropped messages with excessively large tags: "
"https://bugs.unrealircd.org/view.php?id=5947",
) )
def testLineTooLong(self): def testLineTooLong(self):
self.connectClient("bar", capabilities=["message-tags"], skip_if_cap_nak=True) self.connectClient("bar", capabilities=["message-tags"], skip_if_cap_nak=True)

View File

@ -6,8 +6,8 @@ from irctest import cases
class MetadataTestCase(cases.BaseServerTestCase): class MetadataTestCase(cases.BaseServerTestCase):
valid_metadata_keys = {"display-name", "avatar"} valid_metadata_keys = {"valid_key1", "valid_key2"}
invalid_metadata_keys = {"indisplay-name", "inavatar"} invalid_metadata_keys = {"invalid_key1", "invalid_key2"}
@cases.mark_specifications("IRCv3", deprecated=True) @cases.mark_specifications("IRCv3", deprecated=True)
def testInIsupport(self): def testInIsupport(self):
@ -36,7 +36,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
def testGetOneUnsetValid(self): def testGetOneUnsetValid(self):
"""<http://ircv3.net/specs/core/metadata-3.2.html#metadata-get>""" """<http://ircv3.net/specs/core/metadata-3.2.html#metadata-get>"""
self.connectClient("foo") self.connectClient("foo")
self.sendLine(1, "METADATA * GET display-name") self.sendLine(1, "METADATA * GET valid_key1")
m = self.getMessage(1) m = self.getMessage(1)
self.assertMessageMatch( self.assertMessageMatch(
m, m,
@ -52,7 +52,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
-- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-get> -- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-get>
""" """
self.connectClient("foo") self.connectClient("foo")
self.sendLine(1, "METADATA * GET display-name avatar") self.sendLine(1, "METADATA * GET valid_key1 valid_key2")
m = self.getMessage(1) m = self.getMessage(1)
self.assertMessageMatch( self.assertMessageMatch(
m, m,
@ -62,10 +62,10 @@ class MetadataTestCase(cases.BaseServerTestCase):
) )
self.assertEqual( self.assertEqual(
m.params[1], m.params[1],
"display-name", "valid_key1",
m, m,
fail_msg="Response to “METADATA * GET display-name avatar" fail_msg="Response to “METADATA * GET valid_key1 valid_key2"
"did not respond to display-name first: {msg}", "did not respond to valid_key1 first: {msg}",
) )
m = self.getMessage(1) m = self.getMessage(1)
self.assertMessageMatch( self.assertMessageMatch(
@ -76,10 +76,10 @@ class MetadataTestCase(cases.BaseServerTestCase):
) )
self.assertEqual( self.assertEqual(
m.params[1], m.params[1],
"avatar", "valid_key2",
m, m,
fail_msg="Response to “METADATA * GET display-name avatar" fail_msg="Response to “METADATA * GET valid_key1 valid_key2"
"did not respond to avatar as second response: {msg}", "did not respond to valid_key2 as second response: {msg}",
) )
@cases.mark_specifications("IRCv3", deprecated=True) @cases.mark_specifications("IRCv3", deprecated=True)
@ -135,7 +135,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
) )
self.assertEqual( self.assertEqual(
m.params[1], m.params[1],
"display-name", "valid_key1",
m, m,
fail_msg="Second param of 761 after setting “{expects}” to " fail_msg="Second param of 761 after setting “{expects}” to "
"{}” is not “{expects}”: {msg}.", "{}” is not “{expects}”: {msg}.",
@ -190,7 +190,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
def testSetGetValid(self): def testSetGetValid(self):
"""<http://ircv3.net/specs/core/metadata-3.2.html>""" """<http://ircv3.net/specs/core/metadata-3.2.html>"""
self.connectClient("foo") self.connectClient("foo")
self.assertSetGetValue("*", "display-name", "myvalue") self.assertSetGetValue("*", "valid_key1", "myvalue")
@cases.mark_specifications("IRCv3", deprecated=True) @cases.mark_specifications("IRCv3", deprecated=True)
def testSetGetZeroCharInValue(self): def testSetGetZeroCharInValue(self):
@ -198,7 +198,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
-- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-restrictions> -- <http://ircv3.net/specs/core/metadata-3.2.html#metadata-restrictions>
""" """
self.connectClient("foo") self.connectClient("foo")
self.assertSetGetValue("*", "display-name", "zero->\0<-zero", "zero->\\0<-zero") self.assertSetGetValue("*", "valid_key1", "zero->\0<-zero", "zero->\\0<-zero")
@cases.mark_specifications("IRCv3", deprecated=True) @cases.mark_specifications("IRCv3", deprecated=True)
def testSetGetHeartInValue(self): def testSetGetHeartInValue(self):
@ -209,7 +209,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
self.connectClient("foo") self.connectClient("foo")
self.assertSetGetValue( self.assertSetGetValue(
"*", "*",
"display-name", "valid_key1",
"->{}<-".format(heart), "->{}<-".format(heart),
"zero->{}<-zero".format(heart.encode()), "zero->{}<-zero".format(heart.encode()),
) )
@ -223,7 +223,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
# Sending directly because it is not valid UTF-8 so Python would # Sending directly because it is not valid UTF-8 so Python would
# not like it # not like it
self.clients[1].conn.sendall( self.clients[1].conn.sendall(
b"METADATA * SET display-name " b":invalid UTF-8 ->\xc3<-\r\n" b"METADATA * SET valid_key1 " b":invalid UTF-8 ->\xc3<-\r\n"
) )
commands = {m.command for m in self.getMessages(1)} commands = {m.command for m in self.getMessages(1)}
self.assertNotIn( self.assertNotIn(
@ -233,7 +233,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
"UTF-8 was answered with 761 (RPL_KEYVALUE)", "UTF-8 was answered with 761 (RPL_KEYVALUE)",
) )
self.clients[1].conn.sendall( self.clients[1].conn.sendall(
b"METADATA * SET display-name " b":invalid UTF-8: \xc3\r\n" b"METADATA * SET valid_key1 " b":invalid UTF-8: \xc3\r\n"
) )
commands = {m.command for m in self.getMessages(1)} commands = {m.command for m in self.getMessages(1)}
self.assertNotIn( self.assertNotIn(

View File

@ -1,10 +1,7 @@
""" """
`IRCv3 MONITOR <https://ircv3.net/specs/extensions/monitor>`_ `IRCv3 MONITOR <https://ircv3.net/specs/extensions/monitor>`_
and `IRCv3 extended-monitor` <https://ircv3.net/specs/extensions/extended-monitor>`_
""" """
import pytest
from irctest import cases, runner from irctest import cases, runner
from irctest.client_mock import NoMessageException from irctest.client_mock import NoMessageException
from irctest.numerics import ( from irctest.numerics import (
@ -16,7 +13,7 @@ from irctest.numerics import (
from irctest.patma import ANYSTR, StrRe from irctest.patma import ANYSTR, StrRe
class _BaseMonitorTestCase(cases.BaseServerTestCase): class MonitorTestCase(cases.BaseServerTestCase):
def check_server_support(self): def check_server_support(self):
if "MONITOR" not in self.server_support: if "MONITOR" not in self.server_support:
raise runner.IsupportTokenNotSupported("MONITOR") raise runner.IsupportTokenNotSupported("MONITOR")
@ -45,8 +42,6 @@ class _BaseMonitorTestCase(cases.BaseServerTestCase):
extra_format=(nick,), extra_format=(nick,),
) )
class MonitorTestCase(_BaseMonitorTestCase):
@cases.mark_specifications("IRCv3") @cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR") @cases.mark_isupport("MONITOR")
def testMonitorOneDisconnected(self): def testMonitorOneDisconnected(self):
@ -300,11 +295,10 @@ class MonitorTestCase(_BaseMonitorTestCase):
self.sendLine(2, "NICK qux") self.sendLine(2, "NICK qux")
self.getMessages(2) self.getMessages(2)
mononline = self.getMessages(1)[0] mononline = self.getMessages(1)[0]
self.assertMessageMatch( self.assertEqual(mononline.command, RPL_MONONLINE)
mononline, self.assertEqual(len(mononline.params), 2, mononline.params)
command=RPL_MONONLINE, self.assertIn(mononline.params[0], ("bar", "*"))
params=[StrRe(r"(bar|\*)"), StrRe("qux(!.*)?")], self.assertEqual(mononline.params[1].split("!")[0], "qux")
)
# no numerics for a case change # no numerics for a case change
self.sendLine(2, "NICK QUX") self.sendLine(2, "NICK QUX")
@ -315,246 +309,7 @@ class MonitorTestCase(_BaseMonitorTestCase):
self.getMessages(2) self.getMessages(2)
monoffline = self.getMessages(1)[0] monoffline = self.getMessages(1)[0]
# should get RPL_MONOFFLINE with the current unfolded nick # should get RPL_MONOFFLINE with the current unfolded nick
self.assertMessageMatch( self.assertEqual(monoffline.command, RPL_MONOFFLINE)
monoffline, self.assertEqual(len(monoffline.params), 2, monoffline.params)
command=RPL_MONOFFLINE, self.assertIn(monoffline.params[0], ("bar", "*"))
params=[StrRe(r"(bar|\*)"), "QUX"], self.assertEqual(monoffline.params[1].split("!")[0], "QUX")
)
class _BaseExtendedMonitorTestCase(_BaseMonitorTestCase):
def _setupExtendedMonitor(self, monitor_before_connect, watcher_caps, watched_caps):
"""Tests https://ircv3.net/specs/extensions/extended-monitor.html"""
self.connectClient(
"foo",
capabilities=["draft/extended-monitor", *watcher_caps],
skip_if_cap_nak=True,
)
if monitor_before_connect:
self.sendLine(1, "MONITOR + bar")
self.getMessages(1)
self.connectClient("bar", capabilities=watched_caps, skip_if_cap_nak=True)
self.getMessages(2)
else:
self.connectClient("bar", capabilities=watched_caps, skip_if_cap_nak=True)
self.getMessages(2)
self.sendLine(1, "MONITOR + bar")
self.assertMononline(1, "bar")
self.assertEqual(self.getMessages(1), [])
class ExtendedMonitorTestCase(_BaseExtendedMonitorTestCase):
@cases.mark_specifications("IRCv3")
@cases.mark_capabilities("extended-monitor", "away-notify")
@pytest.mark.parametrize(
"monitor_before_connect,cap",
[
pytest.param(
monitor_before_connect,
cap,
id=("monitor_before_connect" if monitor_before_connect else "")
+ "-"
+ ("with-cap" if cap else ""),
)
for monitor_before_connect in [True, False]
for cap in [True, False]
],
)
def testExtendedMonitorAway(self, monitor_before_connect, cap):
"""Tests https://ircv3.net/specs/extensions/extended-monitor.html
with https://ircv3.net/specs/extensions/away-notify
"""
if cap:
self._setupExtendedMonitor(
monitor_before_connect, ["away-notify"], ["away-notify"]
)
else:
self._setupExtendedMonitor(monitor_before_connect, ["away-notify"], [])
self.sendLine(2, "AWAY :afk")
self.getMessages(2)
self.assertMessageMatch(
self.getMessage(1), nick="bar", command="AWAY", params=["afk"]
)
self.assertEqual(self.getMessages(1), [], "watcher got unexpected messages")
self.sendLine(2, "AWAY")
self.getMessages(2)
self.assertMessageMatch(
self.getMessage(1), nick="bar", command="AWAY", params=[]
)
self.assertEqual(self.getMessages(1), [], "watcher got unexpected messages")
@cases.mark_specifications("IRCv3")
@cases.mark_capabilities("extended-monitor", "away-notify")
@pytest.mark.parametrize(
"monitor_before_connect,cap",
[
pytest.param(
monitor_before_connect,
cap,
id=("monitor_before_connect" if monitor_before_connect else "")
+ "-"
+ ("with-cap" if cap else ""),
)
for monitor_before_connect in [True, False]
for cap in [True, False]
],
)
def testExtendedMonitorAwayNoCap(self, monitor_before_connect, cap):
"""Tests https://ircv3.net/specs/extensions/extended-monitor.html
does nothing when ``away-notify`` is not enabled by the watcher
"""
if cap:
self._setupExtendedMonitor(monitor_before_connect, [], ["away-notify"])
else:
self._setupExtendedMonitor(monitor_before_connect, [], [])
self.sendLine(2, "AWAY :afk")
self.getMessages(2)
self.assertEqual(self.getMessages(1), [], "watcher got unexpected messages")
self.sendLine(2, "AWAY")
self.getMessages(2)
self.assertEqual(self.getMessages(1), [], "watcher got unexpected messages")
@cases.mark_specifications("IRCv3")
@cases.mark_capabilities("extended-monitor", "setname")
@pytest.mark.parametrize("monitor_before_connect", [True, False])
def testExtendedMonitorSetName(self, monitor_before_connect):
"""Tests https://ircv3.net/specs/extensions/extended-monitor.html
with https://ircv3.net/specs/extensions/setname
"""
self._setupExtendedMonitor(monitor_before_connect, ["setname"], ["setname"])
self.sendLine(2, "SETNAME :new name")
self.getMessages(2)
self.assertMessageMatch(
self.getMessage(1), nick="bar", command="SETNAME", params=["new name"]
)
self.assertEqual(self.getMessages(1), [], "watcher got unexpected messages")
@cases.mark_specifications("IRCv3")
@cases.mark_capabilities("extended-monitor", "setname")
@pytest.mark.parametrize("monitor_before_connect", [True, False])
def testExtendedMonitorSetNameNoCap(self, monitor_before_connect):
"""Tests https://ircv3.net/specs/extensions/extended-monitor.html
does nothing when ``setname`` is not enabled by the watcher
"""
self._setupExtendedMonitor(monitor_before_connect, [], ["setname"])
self.sendLine(2, "SETNAME :new name")
self.getMessages(2)
self.assertEqual(self.getMessages(1), [], "watcher got unexpected messages")
@cases.mark_services
class AuthenticatedExtendedMonitorTestCase(_BaseExtendedMonitorTestCase):
@cases.mark_specifications("IRCv3")
@cases.mark_capabilities("extended-monitor", "account-notify")
@pytest.mark.parametrize(
"monitor_before_connect,cap",
[
pytest.param(
monitor_before_connect,
cap,
id=("monitor_before_connect" if monitor_before_connect else "")
+ "-"
+ ("with-cap" if cap else ""),
)
for monitor_before_connect in [True, False]
for cap in [True, False]
],
)
def testExtendedMonitorAccountNotify(self, monitor_before_connect, cap):
"""Tests https://ircv3.net/specs/extensions/extended-monitor.html
does nothing when ``account-notify`` is not enabled by the watcher
"""
self.controller.registerUser(self, "jilles", "sesame")
if cap:
self._setupExtendedMonitor(
monitor_before_connect,
["account-notify"],
["account-notify", "sasl", "cap-notify"],
)
else:
self._setupExtendedMonitor(
monitor_before_connect, ["account-notify"], ["sasl", "cap-notify"]
)
self.sendLine(2, "AUTHENTICATE PLAIN")
m = self.getRegistrationMessage(2)
self.assertMessageMatch(
m,
command="AUTHENTICATE",
params=["+"],
fail_msg="Sent “AUTHENTICATE PLAIN”, server should have "
"replied with “AUTHENTICATE +”, but instead sent: {msg}",
)
self.sendLine(2, "AUTHENTICATE amlsbGVzAGppbGxlcwBzZXNhbWU=")
m = self.getRegistrationMessage(2)
self.assertMessageMatch(
m,
command="900",
fail_msg="Did not send 900 after correct SASL authentication.",
)
self.getMessages(2)
self.assertMessageMatch(
self.getMessage(1), nick="bar", command="ACCOUNT", params=["jilles"]
)
self.assertEqual(self.getMessages(1), [], "watcher got unexpected messages")
@cases.mark_specifications("IRCv3")
@cases.mark_capabilities("extended-monitor", "account-notify")
@pytest.mark.parametrize(
"monitor_before_connect,cap",
[
pytest.param(
monitor_before_connect,
cap,
id=("monitor_before_connect" if monitor_before_connect else "")
+ "-"
+ ("with-cap" if cap else ""),
)
for monitor_before_connect in [True, False]
for cap in [True, False]
],
)
def testExtendedMonitorAccountNotifyNoCap(self, monitor_before_connect, cap):
"""Tests https://ircv3.net/specs/extensions/extended-monitor.html
does nothing when ``account-notify`` is not enabled by the watcher
"""
self.controller.registerUser(self, "jilles", "sesame")
if cap:
self._setupExtendedMonitor(
monitor_before_connect, [], ["account-notify", "sasl", "cap-notify"]
)
else:
self._setupExtendedMonitor(
monitor_before_connect, [], ["sasl", "cap-notify"]
)
self.sendLine(2, "AUTHENTICATE PLAIN")
m = self.getRegistrationMessage(2)
self.assertMessageMatch(
m,
command="AUTHENTICATE",
params=["+"],
fail_msg="Sent “AUTHENTICATE PLAIN”, server should have "
"replied with “AUTHENTICATE +”, but instead sent: {msg}",
)
self.sendLine(2, "AUTHENTICATE amlsbGVzAGppbGxlcwBzZXNhbWU=")
m = self.getRegistrationMessage(2)
self.assertMessageMatch(
m,
command="900",
fail_msg="Did not send 900 after correct SASL authentication.",
)
self.getMessages(2)
self.assertEqual(self.getMessages(1), [], "watcher got unexpected messages")

View File

@ -1,502 +0,0 @@
"""
`IRCv3 draft message redaction <https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md>`_
"""
import uuid
import pytest
from irctest import cases
from irctest.patma import ANYDICT, ANYSTR, StrRe
CAPABILITIES = [
"message-tags",
"echo-message",
"batch",
"server-time",
"labeled-response",
"draft/message-redaction",
]
@cases.mark_specifications("IRCv3")
@cases.mark_capabilities(*CAPABILITIES)
class ChannelRedactTestCase(cases.BaseServerTestCase):
def _setupRedactTest(self, redacteeId, redacteeNick, chathistory=False):
capabilities = list(CAPABILITIES)
if chathistory:
capabilities.extend(["batch", "draft/chathistory"])
self.connectClient("chanop", capabilities=capabilities, skip_if_cap_nak=True)
self.sendLine(1, "JOIN #chan")
self.connectClient("user", capabilities=capabilities, skip_if_cap_nak=True)
self.sendLine(2, "JOIN #chan")
self.getMessages(2) # synchronize
self.getMessages(1)
self.sendLine(redacteeId, "@label=1234 PRIVMSG #chan :hello there")
echo = self.getMessage(redacteeId)
self.assertMessageMatch(
echo,
tags={"label": "1234", "msgid": StrRe("[^ ]+"), **ANYDICT},
prefix=StrRe(redacteeNick + "!.*"),
command="PRIVMSG",
params=["#chan", "hello there"],
)
msgid = echo.tags["msgid"]
self.assertMessageMatch(
self.getMessage(3 - redacteeId),
tags={"msgid": msgid, **ANYDICT},
prefix=StrRe(redacteeNick + "!.*"),
command="PRIVMSG",
params=["#chan", "hello there"],
)
return msgid
def testRelayOpSelfRedact(self):
"""Channel op writes a message and redacts it themselves."""
msgid = self._setupRedactTest(redacteeId=1, redacteeNick="chanop")
self.sendLine(1, f"REDACT #chan {msgid} :oops")
self.assertMessageMatch(
self.getMessage(1),
prefix=StrRe("chanop!.*"),
command="REDACT",
params=["#chan", msgid, "oops"],
)
self.assertMessageMatch(
self.getMessage(2),
prefix=StrRe("chanop!.*"),
command="REDACT",
params=["#chan", msgid, "oops"],
)
def testRelayOpRedact(self):
"""User writes a message and channel op redacts it."""
msgid = self._setupRedactTest(
redacteeId=2,
redacteeNick="user",
)
self.sendLine(1, f"REDACT #chan {msgid} :spam")
self.assertMessageMatch(
self.getMessage(1),
prefix=StrRe("chanop!.*"),
command="REDACT",
params=["#chan", msgid, "spam"],
)
self.assertMessageMatch(
self.getMessage(2),
prefix=StrRe("chanop!.*"),
command="REDACT",
params=["#chan", msgid, "spam"],
)
def testRelayUserSelfRedact(self):
"""User writes a message and redacts it themselves.
Servers may either accept or reject this."""
msgid = self._setupRedactTest(redacteeId=2, redacteeNick="user")
self.sendLine(2, f"REDACT #chan {msgid} :oops")
msg = self.getMessage(2)
if msg.command == "REDACT":
self.assertMessageMatch(
msg,
prefix=StrRe("user!.*"),
command="REDACT",
params=["#chan", msgid, "oops"],
)
self.assertMessageMatch(
self.getMessage(1),
prefix=StrRe("user!.*"),
command="REDACT",
params=["#chan", msgid, "oops"],
)
else:
self.assertMessageMatch(
msg,
command="FAIL",
params=["REDACT", "REDACT_FORBIDDEN", "#chan", msgid, ANYSTR],
)
self.assertEqual(self.getMessages(1), [])
def testRejectRedactOtherUser(self):
"""Channel op writes a message and a user attempts to redact it."""
msgid = self._setupRedactTest(redacteeId=1, redacteeNick="chanop")
self.sendLine(2, f"REDACT #chan {msgid} :oops")
self.assertMessageMatch(
self.getMessage(2),
command="FAIL",
params=["REDACT", "REDACT_FORBIDDEN", "#chan", msgid, ANYSTR],
)
self.assertEqual(self.getMessages(1), [])
@pytest.mark.parametrize(
"chathistory_requester",
[
pytest.param(1, id="chathistory-to-chanop"),
pytest.param(2, id="chathistory-to-user"),
],
)
def testOpSelfRedactChathistory(self, chathistory_requester):
"""Channel op writes a message and redacts it themselves; both the op
and a regular user check the chathistory afterward.
https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md#chat-history
"""
msgid = self._setupRedactTest(
redacteeId=1, redacteeNick="chanop", chathistory=True
)
self.sendLine(1, f"REDACT #chan {msgid} :oops")
self.assertMessageMatch(
self.getMessage(1),
prefix=StrRe("chanop!.*"),
command="REDACT",
params=["#chan", msgid, "oops"],
)
self.getMessages(1)
self.getMessages(2)
self.sendLine(chathistory_requester, "CHATHISTORY LATEST #chan * 10")
(start_msg, *msgs, end_msg) = self.getMessages(chathistory_requester)
self.assertMessageMatch(
start_msg,
command="BATCH",
params=[StrRe(r"\+.+"), "chathistory", "#chan"],
)
batch_tag = start_msg.params[0][1:]
# remove Ergo's event-playback fallback
msgs = [msg for msg in msgs if not msg.prefix.startswith("HistServ!")]
self.assertMessageMatch(end_msg, command="BATCH", params=["-" + batch_tag])
if len(msgs) == 0:
pass # Server removed the message entirely
elif len(msgs) == 1:
# Server replaced with the REDACT
self.assertMessageMatch(
msgs[0],
prefix=StrRe("sender!.*"),
command="REDACT",
params=["#chan", msgid, "oops"],
)
elif len(msgs) == 2:
# Server appended the REDACT
self.assertMessageMatch(
msgs[0],
tags={"msgid": msgid, **ANYDICT},
command="PRIVMSG",
params=["#chan", msgid, "hello there"],
)
self.assertMessageMatch(
msgs[1],
prefix=StrRe("sender!.*"),
command="REDACT",
params=["#chan", msgid, "oops"],
)
else:
self.assertTrue(False, fail_msg=f"Unexpectedly many messages: {msgs}")
def testOpRedactNonExistant(self):
"""Channel op writes a message and redacts a random non-existant id."""
self._setupRedactTest(redacteeId=1, redacteeNick="chanop")
nonexistent_msgid = str(uuid.uuid4())
self.sendLine(1, f"REDACT #chan {nonexistent_msgid} :oops")
self.assertMessageMatch(
self.getMessage(1),
command="FAIL",
params=["REDACT", "UNKNOWN_MSGID", "#chan", nonexistent_msgid, ANYSTR],
)
self.assertEqual(self.getMessages(2), [])
def testOpRedactWrongChan(self):
"""Channel op writes a message and redacts it, but uses the wrong channel
as target."""
msgid = self._setupRedactTest(redacteeId=1, redacteeNick="chanop")
self.sendLine(1, "JOIN #otherChan")
self.getMessages(1)
self.sendLine(1, f"REDACT #otherChan {msgid} :oops")
msg = self.getMessage(1)
self.assertMessageMatch(
msg,
command="FAIL",
)
if msg.params[1] == "UNKNOWN_MSGID":
self.assertMessageMatch(
msg,
command="FAIL",
params=["REDACT", "UNKNOWN_MSGID", "#otherChan", msgid, ANYSTR],
)
else:
self.assertMessageMatch(
msg,
command="FAIL",
params=["REDACT", "REDACT_FORBIDDEN", "#otherChan", ANYSTR],
)
self.assertEqual(self.getMessages(2), [])
@cases.mark_specifications("IRCv3")
@cases.mark_capabilities(*CAPABILITIES)
@cases.mark_services
@pytest.mark.private_chathistory
class PmRedactTestCase(cases.BaseServerTestCase):
"""Tests REDACT command in private messages between authenticated accounts"""
def _setupRedactTest(self, chathistory=False):
capabilities = [*CAPABILITIES, "sasl"]
if chathistory:
capabilities.extend(["batch", "draft/chathistory"])
self.controller.registerUser(self, "sender", "senderpass")
self.controller.registerUser(self, "recipient", "recipientpass")
self.connectClient(
"sender",
password="senderpass",
capabilities=capabilities,
skip_if_cap_nak=True,
)
self.connectClient(
"recipient",
password="recipientpass",
capabilities=capabilities,
skip_if_cap_nak=True,
)
self.getMessages(2) # synchronize
self.getMessages(1)
self.sendLine(1, "@label=1234 PRIVMSG recipient :hello there")
echo = self.getMessage(1)
self.assertMessageMatch(
echo,
tags={"label": "1234", "msgid": StrRe("[^ ]+"), **ANYDICT},
prefix=StrRe("sender!.*"),
command="PRIVMSG",
params=["recipient", "hello there"],
)
msgid = echo.tags["msgid"]
self.assertMessageMatch(
self.getMessage(2),
tags={"msgid": msgid, **ANYDICT},
prefix=StrRe("sender!.*"),
command="PRIVMSG",
params=["recipient", "hello there"],
)
return msgid
def testRelaySenderRedact(self):
"""Someone writes a message in private and redacts it themselves."""
msgid = self._setupRedactTest()
self.sendLine(1, f"REDACT recipient {msgid} :oops")
self.assertMessageMatch(
self.getMessage(1),
prefix=StrRe("sender!.*"),
command="REDACT",
params=["recipient", msgid, "oops"],
)
self.assertMessageMatch(
self.getMessage(2),
prefix=StrRe("sender!.*"),
command="REDACT",
params=["recipient", msgid, "oops"],
)
def testRelayRecipientRedact(self):
"""Someone writes a message in private and their recipient redacts it.
Servers may either accept or reject this."""
msgid = self._setupRedactTest()
self.sendLine(2, f"REDACT sender {msgid} :oops")
msg = self.getMessage(2)
if msg.command == "REDACT":
self.assertMessageMatch(
msg,
prefix=StrRe("recipient!.*"),
command="REDACT",
params=["sender", msgid, "oops"],
)
self.assertMessageMatch(
self.getMessage(1),
prefix=StrRe("user!.*"),
command="REDACT",
params=["sender", msgid, "oops"],
)
else:
self.assertMessageMatch(
msg,
command="FAIL",
params=[
"REDACT",
StrRe("(REDACT_FORBIDDEN|UNKNOWN_MSGID)"),
"sender",
msgid,
ANYSTR,
],
)
self.assertEqual(self.getMessages(1), [])
@pytest.mark.parametrize("nick", ["sender", "recipient"])
def testRejectRedactOtherUser(self, nick):
"""Someone writes a message in private to someone else and an unrelated person
attempts to redact it."""
msgid = self._setupRedactTest()
self.controller.registerUser(self, "censor", "censorpass")
self.connectClient(
"censor",
password="censorpass",
capabilities=[*CAPABILITIES, "sasl"],
skip_if_cap_nak=True,
)
self.getMessages(3) # synchronize
self.sendLine(3, f"REDACT {nick} {msgid} :oops")
self.assertMessageMatch(
self.getMessage(3),
command="FAIL",
params=[
"REDACT",
StrRe("(REDACT_FORBIDDEN|UNKNOWN_MSGID)"),
nick,
msgid,
ANYSTR,
],
)
self.assertEqual(self.getMessages(1), [])
self.assertEqual(self.getMessages(2), [])
@pytest.mark.parametrize(
"chathistory_requester",
[
pytest.param(1, id="chathistory-to-sender"),
pytest.param(2, id="chathistory-to-recipient"),
],
)
@pytest.mark.private_chathistory
def testSenderRedactChathistory(self, chathistory_requester):
"""Channel op writes a message and redacts it themselves; both the op
and a regular user check the chathistory afterward.
https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md#chat-history
"""
msgid = self._setupRedactTest(chathistory=True)
self.sendLine(1, f"REDACT recipient {msgid} :oops")
self.assertMessageMatch(
self.getMessage(1),
prefix=StrRe("sender!.*"),
command="REDACT",
params=["recipient", msgid, "oops"],
)
self.getMessages(1)
self.getMessages(2)
if chathistory_requester == 1:
others_nick = "recipient"
else:
others_nick = "sender"
self.sendLine(chathistory_requester, f"CHATHISTORY LATEST {others_nick} * 10")
(start_msg, *msgs, end_msg) = self.getMessages(chathistory_requester)
self.assertMessageMatch(
start_msg,
command="BATCH",
params=[StrRe(r"\+.+"), "chathistory", others_nick],
)
batch_tag = start_msg.params[0][1:]
# remove Ergo's event-playback fallback
msgs = [msg for msg in msgs if not msg.prefix.startswith("HistServ!")]
self.assertMessageMatch(end_msg, command="BATCH", params=["-" + batch_tag])
if len(msgs) == 0:
pass # Server removed the message entirely
elif len(msgs) == 1:
# Server replaced with the REDACT
self.assertMessageMatch(
msgs[0],
prefix=StrRe("sender!.*"),
command="REDACT",
params=["recipient", msgid, "oops"],
)
elif len(msgs) == 2:
# Server appended the REDACT
self.assertMessageMatch(
msgs[0],
tags={"msgid": msgid, **ANYDICT},
command="PRIVMSG",
params=["recipient", msgid, "hello there"],
)
self.assertMessageMatch(
msgs[1],
prefix=StrRe("sender!.*"),
command="REDACT",
params=["recipient", msgid, "oops"],
)
else:
self.assertTrue(False, fail_msg=f"Unexpectedly many messages: {msgs}")
def testRedactNonExistant(self):
"""Someone writes a message in private to someone else and redacts a random
non-existant id."""
self._setupRedactTest()
nonexistent_msgid = str(uuid.uuid4())
self.sendLine(1, f"REDACT recipient {nonexistent_msgid} :oops")
self.assertMessageMatch(
self.getMessage(1),
command="FAIL",
params=["REDACT", "UNKNOWN_MSGID", "recipient", nonexistent_msgid, ANYSTR],
)
self.assertEqual(self.getMessages(2), [])
def testOpRedactWrongChan(self):
"""Channel op writes a message and redacts it, but uses the wrong channel
as target."""
msgid = self._setupRedactTest()
self.sendLine(1, "JOIN #otherChan")
self.getMessages(1)
self.sendLine(1, f"REDACT #otherChan {msgid} :oops")
self.assertMessageMatch(
self.getMessage(1),
command="FAIL",
params=["REDACT", "UNKNOWN_MSGID", "#otherChan", msgid, ANYSTR],
)
self.assertEqual(self.getMessages(2), [])

View File

@ -1,21 +1,36 @@
""" """
`Ergo <https://ergo.chat/>`_-specific tests of non-Unicode filtering `Ergo <https://ergo.chat/>`_-specific tests of non-Unicode filtering
TODO: turn this into a test of `IRCv3 UTF8ONLY
<https://ircv3.net/specs/extensions/utf8-only>`_ <https://ircv3.net/specs/extensions/utf8-only>`_
""" """
from irctest import cases, runner from irctest import cases
from irctest.patma import ANYSTR from irctest.patma import ANYSTR
class Utf8TestCase(cases.BaseServerTestCase): class Utf8TestCase(cases.BaseServerTestCase):
@cases.mark_specifications("Ergo") @cases.mark_specifications("Ergo")
def testNonUtf8Filtering(self): def testUtf8Validation(self):
self.connectClient( self.connectClient(
"bar", "bar",
capabilities=["batch", "echo-message", "labeled-response"], capabilities=["batch", "echo-message", "labeled-response"],
) )
self.joinChannel(1, "#qux") self.joinChannel(1, "#qux")
self.sendLine(1, "PRIVMSG #qux hi")
ms = self.getMessages(1)
self.assertMessageMatch(
[m for m in ms if m.command == "PRIVMSG"][0], params=["#qux", "hi"]
)
self.sendLine(1, b"PRIVMSG #qux hi\xaa")
self.assertMessageMatch(
self.getMessage(1),
command="FAIL",
params=["PRIVMSG", "INVALID_UTF8", ANYSTR],
tags={},
)
self.sendLine(1, b"@label=xyz PRIVMSG #qux hi\xaa") self.sendLine(1, b"@label=xyz PRIVMSG #qux hi\xaa")
self.assertMessageMatch( self.assertMessageMatch(
self.getMessage(1), self.getMessage(1),
@ -23,26 +38,3 @@ class Utf8TestCase(cases.BaseServerTestCase):
params=["PRIVMSG", "INVALID_UTF8", ANYSTR], params=["PRIVMSG", "INVALID_UTF8", ANYSTR],
tags={"label": "xyz"}, tags={"label": "xyz"},
) )
@cases.mark_isupport("UTF8ONLY")
def testUtf8Validation(self):
self.connectClient("foo")
self.connectClient("bar")
if "UTF8ONLY" not in self.server_support:
raise runner.IsupportTokenNotSupported("UTF8ONLY")
self.sendLine(1, "PRIVMSG bar hi")
self.getMessages(1) # synchronize
ms = self.getMessages(2)
self.assertMessageMatch(
[m for m in ms if m.command == "PRIVMSG"][0], params=["bar", "hi"]
)
self.sendLine(1, b"PRIVMSG bar hi\xaa")
m = self.getMessage(1)
assert m.command in ("FAIL", "WARN", "ERROR")
if m.command in ("FAIL", "WARN"):
self.assertMessageMatch(m, params=["PRIVMSG", "INVALID_UTF8", ANYSTR])

View File

@ -99,12 +99,6 @@ class _WhoisTestMixin(cases.BaseServerTestCase):
], ],
) )
elif m.command == RPL_WHOISSPECIAL: elif m.command == RPL_WHOISSPECIAL:
services_controller = self.controller.services_controller
if (
services_controller is not None
and services_controller.software_name == "Dlk-Services"
):
continue
# Technically allowed, but it's a bad style to use this without # Technically allowed, but it's a bad style to use this without
# explicit configuration by the operators. # explicit configuration by the operators.
assert False, "RPL_WHOISSPECIAL in use with default configuration" assert False, "RPL_WHOISSPECIAL in use with default configuration"

View File

@ -201,6 +201,10 @@ class WhowasTestCase(cases.BaseServerTestCase):
) )
@cases.mark_specifications("RFC1459", "RFC2812", "Modern") @cases.mark_specifications("RFC1459", "RFC2812", "Modern")
@cases.xfailIfSoftware(
["InspIRCd"],
"Feature not released yet: https://github.com/inspircd/inspircd/pull/1967",
)
def testWhowasMultiple(self): def testWhowasMultiple(self):
""" """
"The history is searched backward, returning the most recent entry first." "The history is searched backward, returning the most recent entry first."
@ -211,6 +215,10 @@ class WhowasTestCase(cases.BaseServerTestCase):
self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2") self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2")
@cases.mark_specifications("RFC1459", "RFC2812", "Modern") @cases.mark_specifications("RFC1459", "RFC2812", "Modern")
@cases.xfailIfSoftware(
["InspIRCd"],
"Feature not released yet: https://github.com/inspircd/inspircd/pull/1968",
)
def testWhowasCount1(self): def testWhowasCount1(self):
""" """
"If there are multiple entries, up to <count> replies will be returned" "If there are multiple entries, up to <count> replies will be returned"
@ -221,6 +229,10 @@ class WhowasTestCase(cases.BaseServerTestCase):
self._testWhowasMultiple(second_result=False, whowas_command="WHOWAS nick2 1") self._testWhowasMultiple(second_result=False, whowas_command="WHOWAS nick2 1")
@cases.mark_specifications("RFC1459", "RFC2812", "Modern") @cases.mark_specifications("RFC1459", "RFC2812", "Modern")
@cases.xfailIfSoftware(
["InspIRCd"],
"Feature not released yet: https://github.com/inspircd/inspircd/pull/1968",
)
def testWhowasCount2(self): def testWhowasCount2(self):
""" """
"If there are multiple entries, up to <count> replies will be returned" "If there are multiple entries, up to <count> replies will be returned"
@ -231,6 +243,10 @@ class WhowasTestCase(cases.BaseServerTestCase):
self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 2") self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 2")
@cases.mark_specifications("RFC1459", "RFC2812", "Modern") @cases.mark_specifications("RFC1459", "RFC2812", "Modern")
@cases.xfailIfSoftware(
["InspIRCd"],
"Feature not released yet: https://github.com/inspircd/inspircd/pull/1968",
)
def testWhowasCountNegative(self): def testWhowasCountNegative(self):
""" """
"If a non-positive number is passed as being <count>, then a full search "If a non-positive number is passed as being <count>, then a full search
@ -248,6 +264,10 @@ class WhowasTestCase(cases.BaseServerTestCase):
@cases.xfailIfSoftware( @cases.xfailIfSoftware(
["ircu2"], "Fix not released yet: https://github.com/UndernetIRC/ircu2/pull/19" ["ircu2"], "Fix not released yet: https://github.com/UndernetIRC/ircu2/pull/19"
) )
@cases.xfailIfSoftware(
["InspIRCd"],
"Feature not released yet: https://github.com/inspircd/inspircd/pull/1967",
)
def testWhowasCountZero(self): def testWhowasCountZero(self):
""" """
"If a non-positive number is passed as being <count>, then a full search "If a non-positive number is passed as being <count>, then a full search

View File

@ -27,20 +27,16 @@ class Specifications(enum.Enum):
@enum.unique @enum.unique
class Capabilities(enum.Enum): class Capabilities(enum.Enum):
ACCOUNT_NOTIFY = "account-notify"
ACCOUNT_TAG = "account-tag" ACCOUNT_TAG = "account-tag"
AWAY_NOTIFY = "away-notify" AWAY_NOTIFY = "away-notify"
BATCH = "batch" BATCH = "batch"
ECHO_MESSAGE = "echo-message" ECHO_MESSAGE = "echo-message"
EXTENDED_JOIN = "extended-join" EXTENDED_JOIN = "extended-join"
EXTENDED_MONITOR = "extended-monitor"
LABELED_RESPONSE = "labeled-response" LABELED_RESPONSE = "labeled-response"
MESSAGE_REDACTION = "draft/message-redaction"
MESSAGE_TAGS = "message-tags" MESSAGE_TAGS = "message-tags"
MULTILINE = "draft/multiline" MULTILINE = "draft/multiline"
MULTI_PREFIX = "multi-prefix" MULTI_PREFIX = "multi-prefix"
SERVER_TIME = "server-time" SERVER_TIME = "server-time"
SETNAME = "setname"
STS = "sts" STS = "sts"
@classmethod @classmethod
@ -60,7 +56,6 @@ class IsupportTokens(enum.Enum):
MONITOR = "MONITOR" MONITOR = "MONITOR"
STATUSMSG = "STATUSMSG" STATUSMSG = "STATUSMSG"
TARGMAX = "TARGMAX" TARGMAX = "TARGMAX"
UTF8ONLY = "UTF8ONLY"
WHOX = "WHOX" WHOX = "WHOX"
@classmethod @classmethod

View File

@ -65,7 +65,7 @@ def get_install_steps(*, software_config, software_id, version_flavor):
install_steps = [ install_steps = [
{ {
"name": f"Checkout {name}", "name": f"Checkout {name}",
"uses": "actions/checkout@v3", "uses": "actions/checkout@v2",
"with": { "with": {
"repository": software_config["repository"], "repository": software_config["repository"],
"ref": ref, "ref": ref,
@ -94,7 +94,7 @@ def get_build_job(*, software_config, software_id, version_flavor):
cache = [ cache = [
{ {
"name": "Cache dependencies", "name": "Cache dependencies",
"uses": "actions/cache@v3", "uses": "actions/cache@v2",
"with": { "with": {
"path": f"~/.cache\n${{ github.workspace }}/{path}\n", "path": f"~/.cache\n${{ github.workspace }}/{path}\n",
"key": "3-${{ runner.os }}-" "key": "3-${{ runner.os }}-"
@ -116,17 +116,17 @@ def get_build_job(*, software_config, software_id, version_flavor):
return None return None
return { return {
"runs-on": "ubuntu-20.04", "runs-on": "ubuntu-latest",
"steps": [ "steps": [
{ {
"name": "Create directories", "name": "Create directories",
"run": "cd ~/; mkdir -p .local/ go/", "run": "cd ~/; mkdir -p .local/ go/",
}, },
*cache, *cache,
{"uses": "actions/checkout@v3"}, {"uses": "actions/checkout@v2"},
{ {
"name": "Set up Python 3.7", "name": "Set up Python 3.7",
"uses": "actions/setup-python@v4", "uses": "actions/setup-python@v2",
"with": {"python-version": 3.7}, "with": {"python-version": 3.7},
}, },
*install_steps, *install_steps,
@ -159,7 +159,7 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
downloads.append( downloads.append(
{ {
"name": "Download build artefacts", "name": "Download build artefacts",
"uses": "actions/download-artifact@v3", "uses": "actions/download-artifact@v2",
"with": {"name": f"installed-{software_id}", "path": "~"}, "with": {"name": f"installed-{software_id}", "path": "~"},
} }
) )
@ -191,13 +191,13 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
unpack = [] unpack = []
return { return {
"runs-on": "ubuntu-20.04", "runs-on": "ubuntu-latest",
"needs": needs, "needs": needs,
"steps": [ "steps": [
{"uses": "actions/checkout@v3"}, {"uses": "actions/checkout@v2"},
{ {
"name": "Set up Python 3.7", "name": "Set up Python 3.7",
"uses": "actions/setup-python@v4", "uses": "actions/setup-python@v2",
"with": {"python-version": 3.7}, "with": {"python-version": 3.7},
}, },
*downloads, *downloads,
@ -231,7 +231,7 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
{ {
"name": "Publish results", "name": "Publish results",
"if": "always()", "if": "always()",
"uses": "actions/upload-artifact@v3", "uses": "actions/upload-artifact@v2",
"with": { "with": {
"name": f"pytest-results_{test_id}_{version_flavor.value}", "name": f"pytest-results_{test_id}_{version_flavor.value}",
"path": "pytest.xml", "path": "pytest.xml",
@ -250,7 +250,7 @@ def upload_steps(software_id):
}, },
{ {
"name": "Upload build artefacts", "name": "Upload build artefacts",
"uses": "actions/upload-artifact@v3", "uses": "actions/upload-artifact@v2",
"with": { "with": {
"name": f"installed-{software_id}", "name": f"installed-{software_id}",
"path": "~/artefacts-*.tar.gz", "path": "~/artefacts-*.tar.gz",
@ -263,6 +263,7 @@ def upload_steps(software_id):
def generate_workflow(config: dict, version_flavor: VersionFlavor): def generate_workflow(config: dict, version_flavor: VersionFlavor):
on: dict on: dict
if version_flavor == VersionFlavor.STABLE: if version_flavor == VersionFlavor.STABLE:
on = {"push": None, "pull_request": None} on = {"push": None, "pull_request": None}
@ -306,15 +307,15 @@ def generate_workflow(config: dict, version_flavor: VersionFlavor):
jobs["publish-test-results"] = { jobs["publish-test-results"] = {
"name": "Publish Dashboard", "name": "Publish Dashboard",
"needs": sorted({f"test-{test_id}" for test_id in config["tests"]} & set(jobs)), "needs": sorted({f"test-{test_id}" for test_id in config["tests"]} & set(jobs)),
"runs-on": "ubuntu-20.04", "runs-on": "ubuntu-latest",
# the build-and-test job might be skipped, we don't need to run # the build-and-test job might be skipped, we don't need to run
# this job then # this job then
"if": "success() || failure()", "if": "success() || failure()",
"steps": [ "steps": [
{"uses": "actions/checkout@v3"}, {"uses": "actions/checkout@v2"},
{ {
"name": "Download Artifacts", "name": "Download Artifacts",
"uses": "actions/download-artifact@v3", "uses": "actions/download-artifact@v2",
"with": {"path": "artifacts"}, "with": {"path": "artifacts"},
}, },
{ {

View File

@ -12,9 +12,6 @@ disallow_untyped_defs = False
[mypy-irctest.client_tests.*] [mypy-irctest.client_tests.*]
disallow_untyped_defs = False disallow_untyped_defs = False
[mypy-irctest.self_tests.*]
disallow_untyped_defs = False
[mypy-defusedxml.*] [mypy-defusedxml.*]
ignore_missing_imports = True ignore_missing_imports = True

View File

@ -18,20 +18,16 @@ markers =
private_chathistory private_chathistory
# capabilities # capabilities
account-notify
account-tag account-tag
away-notify away-notify
batch batch
echo-message echo-message
extended-join extended-join
extended-monitor
labeled-response labeled-response
draft/message-redaction
message-tags message-tags
draft/multiline draft/multiline
multi-prefix multi-prefix
server-time server-time
setname
sts sts
# isupport tokens # isupport tokens
@ -42,7 +38,6 @@ markers =
PREFIX PREFIX
STATUSMSG STATUSMSG
TARGMAX TARGMAX
UTF8ONLY
WHOX WHOX
python_classes = *TestCase Test* python_classes = *TestCase Test*

View File

@ -42,7 +42,7 @@ def partial_compaction(d):
# tests separate # tests separate
compacted_d = {} compacted_d = {}
successes = [] successes = []
for k, v in d.items(): for (k, v) in d.items():
if isinstance(v, CompactedResult) and v.success and v.nb_skipped == 0: if isinstance(v, CompactedResult) and v.success and v.nb_skipped == 0:
successes.append((k, v)) successes.append((k, v))
else: else:

View File

@ -143,7 +143,7 @@ software:
name: InspIRCd name: InspIRCd
repository: inspircd/inspircd repository: inspircd/inspircd
refs: &inspircd_refs refs: &inspircd_refs
stable: v3.15.0 stable: v3.12.0
release: null release: null
devel: master devel: master
devel_release: insp3 devel_release: insp3
@ -153,13 +153,9 @@ software:
separate_build_job: true separate_build_job: true
build_script: &inspircd_build_script | build_script: &inspircd_build_script |
cd $GITHUB_WORKSPACE/inspircd/ cd $GITHUB_WORKSPACE/inspircd/
patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch
# Insp3 <= 3.16.0 and Insp4 <= 4.0.0a21 don't support -DINSPIRCD_UNLIMITED_MAINLOOP
patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch || true
./configure --prefix=$HOME/.local/inspircd --development ./configure --prefix=$HOME/.local/inspircd --development
make -j 4
CXXFLAGS=-DINSPIRCD_UNLIMITED_MAINLOOP make -j 4
make install make install
irc2: irc2:
name: irc2 name: irc2
@ -272,8 +268,8 @@ software:
name: UnrealIRCd 6 name: UnrealIRCd 6
repository: unrealircd/unrealircd repository: unrealircd/unrealircd
refs: refs:
stable: da3c1c654481a33035b9c703957e1c25d0158259 # 6.0.7 stable: cedd23ae9cdd5985ce16e9869cbdb808479c3fc4 # 6.0.3
release: da3c1c654481a33035b9c703957e1c25d0158259 # 6.0.7 release: cedd23ae9cdd5985ce16e9869cbdb808479c3fc4 # 6.0.3
devel: unreal60_dev devel: unreal60_dev
devel_release: null devel_release: null
path: unrealircd path: unrealircd
@ -289,7 +285,6 @@ software:
CFLAGS="-O0 -march=x86-64" CXXFLAGS="$CFLAGS" ./Config -quick CFLAGS="-O0 -march=x86-64" CXXFLAGS="$CFLAGS" ./Config -quick
make -j 4 make -j 4
make install make install
~/.local/unrealircd/unrealircd module install third/react
# Prevent download of geoIP database on first startup # Prevent download of geoIP database on first startup
sed -i 's/loadmodule "geoip_classic";//' ~/.local/unrealircd/conf/modules.default.conf sed -i 's/loadmodule "geoip_classic";//' ~/.local/unrealircd/conf/modules.default.conf