19 Commits

Author SHA1 Message Date
6559bb34c0 Remove testListTwoParamsTwoChannels's assertions from testListTwoParams 2023-04-08 18:59:52 +02:00
67f08fb211 what about two masks? 2023-04-08 18:22:45 +02:00
199d15b434 testListTwoChannels doesn't need ELIST=U 2023-04-08 18:01:22 +02:00
b2e8f5d1e1 list: Add test for two channels and/or two params 2023-04-08 17:39:11 +02:00
136a7923c0 Bump linter versions (#188)
The isort we had has some weird poetry issue, I figured I might as well
bump the other linters at the same time

```
[INFO] Installing environment for https://github.com/PyCQA/isort.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
An unexpected error has occurred: CalledProcessError: command: ('/home/runner/.cache/pre-commit/repo0m3eczdf/py_env-python3.7/bin/python', '-mpip', 'install', '.')
return code: 1
stdout:
    Processing /home/runner/.cache/pre-commit/repo0m3eczdf
      Installing build dependencies: started
      Installing build dependencies: finished with status 'done'
      Getting requirements to build wheel: started
      Getting requirements to build wheel: finished with status 'done'
      Preparing metadata (pyproject.toml): started
      Preparing metadata (pyproject.toml): finished with status 'error'

stderr:
      error: subprocess-exited-with-error

      × Preparing metadata (pyproject.toml) did not run successfully.
      │ exit code: 1
      ╰─> [14 lines of output]
          Traceback (most recent call last):
            File "/home/runner/.cache/pre-commit/repo0m3eczdf/py_env-python3.7/lib/python3.7/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
              main()
            File "/home/runner/.cache/pre-commit/repo0m3eczdf/py_env-python3.7/lib/python3.7/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
              json_out['return_val'] = hook(**hook_input['kwargs'])
            File "/home/runner/.cache/pre-commit/repo0m3eczdf/py_env-python3.7/lib/python3.7/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 149, in prepare_metadata_for_build_wheel
              return hook(metadata_directory, config_settings)
            File "/tmp/pip-build-env-beaf5dxh/overlay/lib/python3.7/site-packages/poetry/core/masonry/api.py", line 40, in prepare_metadata_for_build_wheel
              poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False)
            File "/tmp/pip-build-env-beaf5dxh/overlay/lib/python3.7/site-packages/poetry/core/factory.py", line 57, in create_poetry
              raise RuntimeError("The Poetry configuration is invalid:\n" + message)
          RuntimeError: The Poetry configuration is invalid:
            - [extras.pipfile_deprecated_finder.2] 'pip-shims<=0.3.4' does not match '^[a-zA-Z-_.0-9]+$'

          [end of output]

      note: This error originates from a subprocess, and is likely not a problem with pip.
    error: metadata-generation-failed

    × Encountered error while generating package metadata.
    ╰─> See above for output.

    note: This is an issue with the package mentioned above, not pip.
    hint: See above for details.
```
2023-03-04 10:51:40 +01:00
5364f963ae Add tests for draft/extended-monitor (#180) 2023-03-04 10:11:51 +01:00
1ea3e1c15c Fix insp4 support after 'helpop' config file was renamed (#187)
c2e954903a
2023-03-01 20:07:58 +01:00
8530c85adc sopel: remove use of deprecated argument
it's removed in aceedf5837
2023-02-15 19:11:51 +01:00
6815dd238b Fix race condition on Ergo 2023-02-11 22:26:23 +01:00
00562ff82d Run utf8 tests on servers which advertise UTF8ONLY (#185) 2023-01-28 10:12:32 +01:00
b7e8a7a5f5 direct message tests (#184)
* Test privmsg to non-existent user

* Test privmsg to user

* fix synchronization issue

* apply black

Co-authored-by: ma-anwar <ma.rizvi.anwar@gmail.com>
2023-01-22 07:45:25 -05:00
6181dd07ad Skip failure on RPL_WHOISSPECIAL with Dlk-Services 2022-12-16 19:09:09 +01:00
5fe4d4cfd8 github: Force ubuntu-20.04
Bahamut does not support ubuntu-22.04
2022-12-06 20:59:27 +01:00
544ca4b7ed Update flake8 URL
The Gitlab.com repo was removed today
2022-12-03 08:57:04 +01:00
35d342a478 account_registration: Add missing 'services' mark 2022-11-20 23:33:20 +01:00
29e4c2bbdb Hardcode DH parameters
openssl version in ubuntu 22.04 forbids moduli smaller than 512,
which would take longer to generate.
2022-11-18 18:57:51 +01:00
fd0b050686 Add support for Dlk-Services (#176) 2022-11-14 22:58:30 +01:00
d0645ab1a8 dashboard: Use qualified class names in multi-module views 2022-11-12 11:49:14 +01:00
65d7e0e506 whowas: Update quotes and links to Modern spec
In particular, this takes https://github.com/ircdocs/modern-irc/pull/196
into account.
2022-10-22 15:49:30 +02:00
28 changed files with 1008 additions and 154 deletions

View File

@ -3,7 +3,7 @@
jobs: jobs:
build-anope: build-anope:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -43,7 +43,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-bahamut: build-bahamut:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -92,7 +92,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-hybrid: build-hybrid:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -131,7 +131,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-inspircd: build-inspircd:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -162,7 +162,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-ngircd: build-ngircd:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -203,7 +203,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-plexus4: build-plexus4:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -245,7 +245,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-solanum: build-solanum:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -285,7 +285,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-unrealircd: build-unrealircd:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -331,7 +331,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-unrealircd-5: build-unrealircd-5:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -400,7 +400,8 @@ jobs:
- test-unrealircd-5 - test-unrealircd-5
- test-unrealircd-anope - test-unrealircd-anope
- test-unrealircd-atheme - test-unrealircd-atheme
runs-on: ubuntu-latest - test-unrealircd-dlk
runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Download Artifacts - name: Download Artifacts
@ -427,7 +428,7 @@ jobs:
test-bahamut: test-bahamut:
needs: needs:
- build-bahamut - build-bahamut
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -461,7 +462,7 @@ jobs:
needs: needs:
- build-bahamut - build-bahamut
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -499,7 +500,7 @@ jobs:
test-bahamut-atheme: test-bahamut-atheme:
needs: needs:
- build-bahamut - build-bahamut
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -531,7 +532,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-ergo: test-ergo:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -573,7 +574,7 @@ jobs:
needs: needs:
- build-hybrid - build-hybrid
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -611,7 +612,7 @@ jobs:
test-inspircd: test-inspircd:
needs: needs:
- build-inspircd - build-inspircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -645,7 +646,7 @@ jobs:
needs: needs:
- build-inspircd - build-inspircd
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -682,7 +683,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-ircu2: test-ircu2:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -721,7 +722,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-limnoria: test-limnoria:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -749,7 +750,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-nefarious: test-nefarious:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -788,7 +789,7 @@ jobs:
test-ngircd: test-ngircd:
needs: needs:
- build-ngircd - build-ngircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -822,7 +823,7 @@ jobs:
needs: needs:
- build-ngircd - build-ngircd
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -860,7 +861,7 @@ jobs:
test-ngircd-atheme: test-ngircd-atheme:
needs: needs:
- build-ngircd - build-ngircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -894,7 +895,7 @@ jobs:
needs: needs:
- build-plexus4 - build-plexus4
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -932,7 +933,7 @@ jobs:
test-solanum: test-solanum:
needs: needs:
- build-solanum - build-solanum
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -964,7 +965,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-sopel: test-sopel:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -992,7 +993,7 @@ jobs:
test-unrealircd: test-unrealircd:
needs: needs:
- build-unrealircd - build-unrealircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1025,7 +1026,7 @@ jobs:
test-unrealircd-5: test-unrealircd-5:
needs: needs:
- build-unrealircd-5 - build-unrealircd-5
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1059,7 +1060,7 @@ jobs:
needs: needs:
- build-unrealircd - build-unrealircd
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1097,7 +1098,7 @@ jobs:
test-unrealircd-atheme: test-unrealircd-atheme:
needs: needs:
- build-unrealircd - build-unrealircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1127,6 +1128,52 @@ jobs:
with: with:
name: pytest-results_unrealircd-atheme_devel name: pytest-results_unrealircd-atheme_devel
path: pytest.xml path: pytest.xml
test-unrealircd-dlk:
needs:
- build-unrealircd
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Download build artefacts
uses: actions/download-artifact@v2
with:
name: installed-unrealircd
path: '~'
- name: Unpack artefacts
run: cd ~; find -name 'artefacts-*.tar.gz' -exec tar -xzf '{}' \;
- name: Checkout Dlk
uses: actions/checkout@v2
with:
path: Dlk-Services
ref: main
repository: DalekIRC/Dalek-Services
- name: Build Dlk
run: |
pip install pifpaf
wget -q https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
wget -q https://wordpress.org/latest.zip -O wordpress-latest.zip
- name: Install system dependencies
run: sudo apt-get install atheme-services faketime
- name: Install irctest dependencies
run: |-
python -m pip install --upgrade pip
pip install pytest pytest-xdist -r requirements.txt
- name: Test with pytest
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
IRCTEST_DLK_PATH="${{ github.workspace }}/Dlk-Services" IRCTEST_WP_CLI_PATH="${{
github.workspace }}/wp-cli.phar" IRCTEST_WP_ZIP_PATH="${{ github.workspace
}}/wordpress-latest.zip" make unrealircd-dlk
timeout-minutes: 30
- if: always()
name: Publish results
uses: actions/upload-artifact@v2
with:
name: pytest-results_unrealircd-dlk_devel
path: pytest.xml
name: irctest with devel versions name: irctest with devel versions
'on': 'on':
schedule: schedule:

View File

@ -3,7 +3,7 @@
jobs: jobs:
build-anope: build-anope:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -43,7 +43,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-inspircd: build-inspircd:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -80,7 +80,7 @@ jobs:
- test-inspircd - test-inspircd
- test-inspircd-anope - test-inspircd-anope
- test-inspircd-atheme - test-inspircd-atheme
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Download Artifacts - name: Download Artifacts
@ -107,7 +107,7 @@ jobs:
test-inspircd: test-inspircd:
needs: needs:
- build-inspircd - build-inspircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -141,7 +141,7 @@ jobs:
needs: needs:
- build-inspircd - build-inspircd
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -179,7 +179,7 @@ jobs:
test-inspircd-atheme: test-inspircd-atheme:
needs: needs:
- build-inspircd - build-inspircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7

View File

@ -3,7 +3,7 @@
jobs: jobs:
build-anope: build-anope:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -43,7 +43,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-bahamut: build-bahamut:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -92,7 +92,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-charybdis: build-charybdis:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -132,7 +132,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-hybrid: build-hybrid:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -171,7 +171,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-inspircd: build-inspircd:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -202,7 +202,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-ngircd: build-ngircd:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -243,7 +243,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-plexus4: build-plexus4:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -285,7 +285,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-solanum: build-solanum:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -325,7 +325,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-unrealircd: build-unrealircd:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -371,7 +371,7 @@ jobs:
path: ~/artefacts-*.tar.gz path: ~/artefacts-*.tar.gz
retention-days: 1 retention-days: 1
build-unrealircd-5: build-unrealircd-5:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Create directories - name: Create directories
run: cd ~/; mkdir -p .local/ go/ run: cd ~/; mkdir -p .local/ go/
@ -443,7 +443,8 @@ jobs:
- test-unrealircd-5 - test-unrealircd-5
- test-unrealircd-anope - test-unrealircd-anope
- test-unrealircd-atheme - test-unrealircd-atheme
runs-on: ubuntu-latest - test-unrealircd-dlk
runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Download Artifacts - name: Download Artifacts
@ -470,7 +471,7 @@ jobs:
test-bahamut: test-bahamut:
needs: needs:
- build-bahamut - build-bahamut
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -504,7 +505,7 @@ jobs:
needs: needs:
- build-bahamut - build-bahamut
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -542,7 +543,7 @@ jobs:
test-bahamut-atheme: test-bahamut-atheme:
needs: needs:
- build-bahamut - build-bahamut
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -575,7 +576,7 @@ jobs:
test-charybdis: test-charybdis:
needs: needs:
- build-charybdis - build-charybdis
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -607,7 +608,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-ergo: test-ergo:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -649,7 +650,7 @@ jobs:
needs: needs:
- build-hybrid - build-hybrid
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -687,7 +688,7 @@ jobs:
test-inspircd: test-inspircd:
needs: needs:
- build-inspircd - build-inspircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -721,7 +722,7 @@ jobs:
needs: needs:
- build-inspircd - build-inspircd
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -759,7 +760,7 @@ jobs:
test-inspircd-atheme: test-inspircd-atheme:
needs: needs:
- build-inspircd - build-inspircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -791,7 +792,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-irc2: test-irc2:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -841,7 +842,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-ircu2: test-ircu2:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -880,7 +881,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-limnoria: test-limnoria:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -907,7 +908,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-nefarious: test-nefarious:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -946,7 +947,7 @@ jobs:
test-ngircd: test-ngircd:
needs: needs:
- build-ngircd - build-ngircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -980,7 +981,7 @@ jobs:
needs: needs:
- build-ngircd - build-ngircd
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1018,7 +1019,7 @@ jobs:
test-ngircd-atheme: test-ngircd-atheme:
needs: needs:
- build-ngircd - build-ngircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1052,7 +1053,7 @@ jobs:
needs: needs:
- build-plexus4 - build-plexus4
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1090,7 +1091,7 @@ jobs:
test-solanum: test-solanum:
needs: needs:
- build-solanum - build-solanum
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1122,7 +1123,7 @@ jobs:
path: pytest.xml path: pytest.xml
test-sopel: test-sopel:
needs: [] needs: []
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1150,7 +1151,7 @@ jobs:
test-unrealircd: test-unrealircd:
needs: needs:
- build-unrealircd - build-unrealircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1183,7 +1184,7 @@ jobs:
test-unrealircd-5: test-unrealircd-5:
needs: needs:
- build-unrealircd-5 - build-unrealircd-5
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1217,7 +1218,7 @@ jobs:
needs: needs:
- build-unrealircd - build-unrealircd
- build-anope - build-anope
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1255,7 +1256,7 @@ jobs:
test-unrealircd-atheme: test-unrealircd-atheme:
needs: needs:
- build-unrealircd - build-unrealircd
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.7 - name: Set up Python 3.7
@ -1285,6 +1286,52 @@ jobs:
with: with:
name: pytest-results_unrealircd-atheme_stable name: pytest-results_unrealircd-atheme_stable
path: pytest.xml path: pytest.xml
test-unrealircd-dlk:
needs:
- build-unrealircd
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Download build artefacts
uses: actions/download-artifact@v2
with:
name: installed-unrealircd
path: '~'
- name: Unpack artefacts
run: cd ~; find -name 'artefacts-*.tar.gz' -exec tar -xzf '{}' \;
- name: Checkout Dlk
uses: actions/checkout@v2
with:
path: Dlk-Services
ref: effd18652fc1c847d1959089d9cca9ff9837a8c0
repository: DalekIRC/Dalek-Services
- name: Build Dlk
run: |
pip install pifpaf
wget -q https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
wget -q https://wordpress.org/latest.zip -O wordpress-latest.zip
- name: Install system dependencies
run: sudo apt-get install atheme-services faketime
- name: Install irctest dependencies
run: |-
python -m pip install --upgrade pip
pip install pytest pytest-xdist -r requirements.txt
- name: Test with pytest
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
IRCTEST_DLK_PATH="${{ github.workspace }}/Dlk-Services" IRCTEST_WP_CLI_PATH="${{
github.workspace }}/wp-cli.phar" IRCTEST_WP_ZIP_PATH="${{ github.workspace
}}/wordpress-latest.zip" make unrealircd-dlk
timeout-minutes: 30
- if: always()
name: Publish results
uses: actions/upload-artifact@v2
with:
name: pytest-results_unrealircd-dlk_stable
path: pytest.xml
name: irctest with stable versions name: irctest with stable versions
'on': 'on':
pull_request: null pull_request: null

View File

@ -2,22 +2,23 @@ exclude: ^irctest/scram
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.3.0 rev: 23.1.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.5.2 rev: 5.11.5
hooks: hooks:
- id: isort - id: isort
- repo: https://gitlab.com/pycqa/flake8 - repo: https://github.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: v0.812 rev: v1.0.1
hooks: hooks:
- id: mypy - id: mypy
additional_dependencies: [types-PyYAML, types-docutils]

View File

@ -274,3 +274,10 @@ unrealircd-anope:
--services-controller=irctest.controllers.anope_services \ --services-controller=irctest.controllers.anope_services \
-m 'services' \ -m 'services' \
-k '$(UNREALIRCD_SELECTORS)' -k '$(UNREALIRCD_SELECTORS)'
unrealircd-dlk:
pifpaf run mysql -- $(PYTEST) $(PYTEST_ARGS) \
--controller=irctest.controllers.unrealircd \
--services-controller=irctest.controllers.dlk_services \
-m 'services' \
-k '$(UNREALIRCD_SELECTORS)'

View File

@ -7,6 +7,7 @@ import shutil
import socket import socket
import subprocess import subprocess
import tempfile import tempfile
import textwrap
import time import time
from typing import IO, Any, Callable, Dict, List, Optional, Set, Tuple, Type from typing import IO, Any, Callable, Dict, List, Optional, Set, Tuple, Type
@ -156,10 +157,18 @@ class DirectoryBasedController(_BaseController):
], ],
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
) )
subprocess.check_output( with self.dh_path.open("w") as fd:
[self.openssl_bin, "dhparam", "-out", self.dh_path, "128"], fd.write(
stderr=subprocess.DEVNULL, textwrap.dedent(
) """
-----BEGIN DH PARAMETERS-----
MIGHAoGBAJICSyQAiLj1fw8b5xELcnpqBQ+wvOyKgim4IetWOgZnRQFkTgOeoRZD
HksACRFJL/EqHxDKcy/2Ghwr2axhNxSJ+UOBmraP3WfodV/fCDPnZ+XnI9fjHsIr
rjisPMqomjXeiTB1UeAHvLUmCK4yx6lpAJsCYwJjsqkycUfHiy1bAgEC
-----END DH PARAMETERS-----
"""
)
)
class BaseClientController(_BaseController): class BaseClientController(_BaseController):
@ -301,10 +310,11 @@ class BaseServicesController(_BaseController):
c.sendLine("PONG :" + msg.params[0]) c.sendLine("PONG :" + msg.params[0])
c.getMessages() c.getMessages()
timeout = time.time() + 5 timeout = time.time() + 3
while True: while True:
c.sendLine(f"PRIVMSG {self.server_controller.nickserv} :HELP") c.sendLine(f"PRIVMSG {self.server_controller.nickserv} :help")
msgs = self.getNickServResponse(c)
msgs = self.getNickServResponse(c, timeout=1)
for msg in msgs: for msg in msgs:
if msg.command == "401": if msg.command == "401":
# NickServ not available yet # NickServ not available yet
@ -330,11 +340,12 @@ class BaseServicesController(_BaseController):
c.disconnect() c.disconnect()
self.services_up = True self.services_up = True
def getNickServResponse(self, client: Any) -> List[Message]: def getNickServResponse(self, client: Any, timeout: int = 0) -> List[Message]:
"""Wrapper aroung getMessages() that waits longer, because NickServ """Wrapper aroung getMessages() that waits longer, because NickServ
is queried asynchronously.""" is queried asynchronously."""
msgs: List[Message] = [] msgs: List[Message] = []
while not msgs: start_time = time.time()
while not msgs and (not timeout or start_time + timeout > time.time()):
time.sleep(0.05) time.sleep(0.05)
msgs = client.getMessages() msgs = client.getMessages()
return msgs return msgs

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,7 +448,9 @@ 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, auth: Optional[Authentication] = None, tls_config: tls.TlsConfig = None self,
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(
@ -458,9 +460,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:
@ -689,7 +691,7 @@ class BaseServerTestCase(
def connectClient( def connectClient(
self, self,
nick: str, nick: str,
name: TClientName = None, name: Optional[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,
@ -734,8 +736,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( self.targmax: Dict[str, Optional[str]] = dict( # type: ignore[assignment]
item.split(":", 1) # type: ignore item.split(":", 1)
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

@ -0,0 +1,245 @@
import os
from pathlib import Path
import secrets
import subprocess
from typing import Optional, Type
import irctest
from irctest.basecontrollers import BaseServicesController, DirectoryBasedController
import irctest.cases
import irctest.runner
TEMPLATE_DLK_CONFIG = """\
info {{
SID "00A";
network-name "testnetwork";
services-name "services.example.org";
admin-email "admin@example.org";
}}
link {{
hostname "{server_hostname}";
port "{server_port}";
password "password";
}}
log {{
debug "yes";
}}
sql {{
port "3306";
username "pifpaf";
password "pifpaf";
database "pifpaf";
sockfile "{mysql_socket}";
prefix "{dlk_prefix}";
}}
wordpress {{
prefix "{wp_prefix}";
}}
"""
TEMPLATE_DLK_WP_CONFIG = """
<?php
global $wpconfig;
$wpconfig = [
"dbprefix" => "{wp_prefix}",
"default_avatar" => "https://valware.uk/wp-content/plugins/ultimate-member/assets/img/default_avatar.jpg",
"forumschan" => "#DLK-Support",
];
"""
TEMPLATE_WP_CONFIG = """
define( 'DB_NAME', 'pifpaf' );
define( 'DB_USER', 'pifpaf' );
define( 'DB_PASSWORD', 'pifpaf' );
define( 'DB_HOST', 'localhost:{mysql_socket}' );
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );
define( 'AUTH_KEY', 'put your unique phrase here' );
define( 'SECURE_AUTH_KEY', 'put your unique phrase here' );
define( 'LOGGED_IN_KEY', 'put your unique phrase here' );
define( 'NONCE_KEY', 'put your unique phrase here' );
define( 'AUTH_SALT', 'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT', 'put your unique phrase here' );
define( 'NONCE_SALT', 'put your unique phrase here' );
$table_prefix = '{wp_prefix}';
define( 'WP_DEBUG', false );
if (!defined('ABSPATH')) {{
define( 'ABSPATH', '{wp_path}' );
}}
/* That's all, stop editing! Happy publishing. */
/** Absolute path to the WordPress directory. */
/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
"""
class DlkController(BaseServicesController, DirectoryBasedController):
"""Mixin for server controllers that rely on DLK"""
software_name = "Dlk-Services"
def run_sql(self, sql: str) -> None:
mysql_socket = os.environ["PIFPAF_MYSQL_SOCKET"]
subprocess.run(
["mysql", "-S", mysql_socket, "pifpaf"],
input=sql.encode(),
check=True,
)
def run(self, protocol: str, server_hostname: str, server_port: int) -> None:
self.create_config()
if protocol == "unreal4":
protocol = "unreal5"
assert protocol in ("unreal5",), protocol
mysql_socket = os.environ["PIFPAF_MYSQL_SOCKET"]
assert self.directory
try:
self.wp_cli_path = Path(os.environ["IRCTEST_WP_CLI_PATH"])
if not self.wp_cli_path.is_file():
raise KeyError()
except KeyError:
raise RuntimeError(
"$IRCTEST_WP_CLI_PATH must be set to a WP-CLI executable (eg. "
"downloaded from <https://raw.githubusercontent.com/wp-cli/builds/"
"gh-pages/phar/wp-cli.phar>)"
) from None
try:
self.dlk_path = Path(os.environ["IRCTEST_DLK_PATH"])
if not self.dlk_path.is_dir():
raise KeyError()
except KeyError:
raise RuntimeError("$IRCTEST_DLK_PATH is not set") from None
self.dlk_path = self.dlk_path.resolve()
# Unpack a fresh Wordpress install in the temporary directory.
# In theory we could have a common Wordpress install and only wp-config.php
# in the temporary directory; but wp-cli assumes wp-config.php must be
# in a Wordpress directory, and fails in various places if it isn't.
# Rather than symlinking everything to make it work, let's just copy
# the whole code, it's not that big.
try:
wp_zip_path = Path(os.environ["IRCTEST_WP_ZIP_PATH"])
if not wp_zip_path.is_file():
raise KeyError()
except KeyError:
raise RuntimeError(
"$IRCTEST_WP_ZIP_PATH must be set to a Wordpress source zipball "
"(eg. downloaded from <https://wordpress.org/latest.zip>)"
) from None
subprocess.run(
["unzip", wp_zip_path, "-d", self.directory], stdout=subprocess.DEVNULL
)
self.wp_path = self.directory / "wordpress"
rand_hex = secrets.token_hex(6)
self.wp_prefix = f"wp{rand_hex}_"
self.dlk_prefix = f"dlk{rand_hex}_"
template_vars = dict(
protocol=protocol,
server_hostname=server_hostname,
server_port=server_port,
mysql_socket=mysql_socket,
wp_path=self.wp_path,
wp_prefix=self.wp_prefix,
dlk_prefix=self.dlk_prefix,
)
# Configure Wordpress
wp_config_path = self.directory / "wp-config.php"
with open(wp_config_path, "w") as fd:
fd.write(TEMPLATE_WP_CONFIG.format(**template_vars))
subprocess.run(
[
"php",
self.wp_cli_path,
"core",
"install",
"--url=http://localhost/",
"--title=irctest site",
"--admin_user=adminuser",
"--admin_email=adminuser@example.org",
f"--path={self.wp_path}",
],
check=True,
)
# Configure Dlk
dlk_log_dir = self.directory / "logs"
dlk_conf_dir = self.directory / "conf"
dlk_conf_path = dlk_conf_dir / "dalek.conf"
os.mkdir(dlk_conf_dir)
with open(dlk_conf_path, "w") as fd:
fd.write(TEMPLATE_DLK_CONFIG.format(**template_vars))
dlk_wp_config_path = dlk_conf_dir / "wordpress.conf"
with open(dlk_wp_config_path, "w") as fd:
fd.write(TEMPLATE_DLK_WP_CONFIG.format(**template_vars))
(dlk_conf_dir / "modules.conf").symlink_to(self.dlk_path / "conf/modules.conf")
self.proc = subprocess.Popen(
[
"php",
"src/dalek",
],
cwd=self.dlk_path,
env={
**os.environ,
"DALEK_CONF_DIR": str(dlk_conf_dir),
"DALEK_LOG_DIR": str(dlk_log_dir),
},
)
def terminate(self) -> None:
super().terminate()
def kill(self) -> None:
super().kill()
def registerUser(
self,
case: irctest.cases.BaseServerTestCase,
username: str,
password: Optional[str] = None,
) -> None:
assert password
subprocess.run(
[
"php",
self.wp_cli_path,
"user",
"create",
username,
f"{username}@example.org",
f"--user_pass={password}",
f"--path={self.wp_path}",
],
check=True,
)
def get_irctest_controller_class() -> Type[DlkController]:
return DlkController

View File

@ -1,3 +1,4 @@
import functools
import shutil import shutil
import subprocess import subprocess
from typing import Optional, Set, Type from typing import Optional, Set, Type
@ -80,8 +81,8 @@ TEMPLATE_CONFIG = """
# HELP/HELPOP # HELP/HELPOP
<module name="alias"> # for the HELP alias <module name="alias"> # for the HELP alias
<module name="helpop"> <module name="{help_module_name}">
<include file="examples/helpop.conf.example"> <include file="examples/{help_module_name}.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">
@ -94,6 +95,17 @@ 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"}
@ -138,6 +150,13 @@ 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(
@ -147,6 +166,7 @@ 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

@ -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", "--quiet", "-c", self.filename]) self.proc = subprocess.Popen(["sopel", "-c", self.filename])
def get_irctest_controller_class() -> Type[SopelController]: def get_irctest_controller_class() -> Type[SopelController]:

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): def output_filename(self) -> str:
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 = {} extra: Dict[str, str] = {}
for child in case: for child in case:
if child.tag == "skipped": if child.tag == "skipped":
success = True success = True
@ -173,6 +173,7 @@ def build_module_html(
def build_test_table(jobs: List[str], results: List[CaseResult]) -> ET.Element: def build_test_table(jobs: List[str], results: List[CaseResult]) -> ET.Element:
multiple_modules = len({r.module_name for r in results}) > 1
results_by_module_and_class = group_by( results_by_module_and_class = group_by(
results, lambda r: (r.module_name, r.class_name) results, lambda r: (r.module_name, r.class_name)
) )
@ -186,22 +187,32 @@ 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 the page shows classes from various modules, use the fully-qualified
# name in order to disambiguate and be clearer (eg. show
# "irctest.server_tests.extended_join.MetadataTestCase" instead of just
# "MetadataTestCase" which looks like it's about IRCv3's METADATA spec.
qualified_class_name = f"{module_name}.{class_name}"
else:
# otherwise, it's not needed, so let's not display it
qualified_class_name = class_name
module = importlib.import_module(module_name) module = importlib.import_module(module_name)
# Header row: class name # Header row: class name
header_row = ET.SubElement(table, "tr") header_row = ET.SubElement(table, "tr")
th = ET.SubElement(header_row, "th", colspan=str(len(jobs) + 1)) th = ET.SubElement(header_row, "th", colspan=str(len(jobs) + 1))
row_anchor = f"{class_name}" row_anchor = f"{qualified_class_name}"
section_header = ET.SubElement( section_header = ET.SubElement(
ET.SubElement(th, "h2"), ET.SubElement(th, "h2"),
"a", "a",
href=f"#{row_anchor}", href=f"#{row_anchor}",
id=row_anchor, id=row_anchor,
) )
section_header.text = class_name section_header.text = qualified_class_name
append_docstring(th, getattr(module, class_name)) append_docstring(th, getattr(module, class_name))
# Header row: one column for each implementation # Header row: one column for each implementation
@ -209,8 +220,8 @@ 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"{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
# TODO: only hash test parameter # TODO: only hash test parameter
@ -292,7 +303,7 @@ def write_html_pages(
for result in results for result in results
) )
assert is_client != is_server, (job, is_client, is_server) assert is_client != is_server, (job, is_client, is_server)
if job.endswith(("-atheme", "-anope")): if job.endswith(("-atheme", "-anope", "-dlk")):
assert is_server assert is_server
job_categories[job] = "server-with-services" job_categories[job] = "server-with-services"
elif is_server: elif is_server:
@ -303,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]
@ -355,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":
@ -368,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}")
@ -380,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): def public_download_url(self) -> str:
# 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,6 +9,7 @@ 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
@ -33,6 +34,7 @@ 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
@ -60,6 +62,7 @@ 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
@ -110,6 +113,7 @@ 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

@ -238,6 +238,135 @@ class ListTestCase(_BasedListTestCase):
self.sendLine(3, "LIST <100") self.sendLine(3, "LIST <100")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"}) self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
@cases.mark_specifications("Modern")
def testListTwoChannels(self):
"""
"Parameters: [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]"
-- https://modern.ircdocs.horse/#list-message
"""
self.connectClient("foo")
if "TARGMAX" in self.server_support:
for item in (self.server_support["TARGMAX"]).split(","):
(command, max_) = item.split(":", 1)
if command == "LIST" and int(max_ or "1000") < 2:
raise runner.OptionalExtensionNotSupported("TARGMAX=LIST >= 2")
self.sendLine(1, "JOIN #chan1")
self.getMessages(1)
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self.sendLine(1, "JOIN #chan3")
self.getMessages(1)
self.connectClient("bar")
self.sendLine(2, "JOIN #chan2")
self.getMessages(2)
self.connectClient("baz")
self.sendLine(3, "LIST")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2", "#chan3"})
self.sendLine(3, "LIST #chan1,#chan2")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
@cases.mark_isupport("ELIST")
def testListTwoMasks(self):
self.connectClient("foo")
if "M" not in self.server_support.get("ELIST", ""):
raise runner.OptionalExtensionNotSupported("ELIST=M")
if "TARGMAX" in self.server_support:
for item in (self.server_support["TARGMAX"]).split(","):
(command, max_) = item.split(":", 1)
if command == "LIST" and int(max_ or "1000") < 2:
raise runner.OptionalExtensionNotSupported("TARGMAX=LIST >= 2")
self.sendLine(1, "JOIN #chan1")
self.getMessages(1)
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self.sendLine(1, "JOIN #chan3")
self.getMessages(1)
self.connectClient("bar")
self.sendLine(2, "JOIN #chan2")
self.getMessages(2)
self.connectClient("baz")
self.sendLine(3, "LIST")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2", "#chan3"})
self.sendLine(3, "LIST *an1,*an2")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
@cases.mark_isupport("ELIST")
@cases.mark_specifications("Modern")
def testListTwoParams(self):
"""
"Parameters: [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]"
-- https://modern.ircdocs.horse/#list-message
"""
self.connectClient("foo")
if "U" not in self.server_support.get("ELIST", ""):
raise runner.OptionalExtensionNotSupported("ELIST=U")
self.sendLine(1, "JOIN #chan1")
self.getMessages(1)
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self.connectClient("bar")
self.sendLine(2, "JOIN #chan2")
self.getMessages(2)
self.connectClient("baz")
self.sendLine(3, "LIST #chan1 >0")
self.assertEqual(self._parseChanList(3), {"#chan1"})
self.sendLine(3, "LIST #chan1 <1")
self.assertEqual(self._parseChanList(3), set())
@cases.mark_isupport("ELIST")
@cases.mark_specifications("Modern")
def testListTwoParamsTwoChannels(self):
"""
"Parameters: [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]"
-- https://modern.ircdocs.horse/#list-message
"""
self.connectClient("foo")
if "U" not in self.server_support.get("ELIST", ""):
raise runner.OptionalExtensionNotSupported("ELIST=U")
if "TARGMAX" in self.server_support:
for item in (self.server_support["TARGMAX"]).split(","):
(command, max_) = item.split(":", 1)
if command == "LIST" and int(max_ or "1000") < 2:
raise runner.OptionalExtensionNotSupported("TARGMAX=LIST >= 2")
self.sendLine(1, "JOIN #chan1")
self.getMessages(1)
self.sendLine(1, "JOIN #chan2")
self.getMessages(1)
self.connectClient("bar")
self.sendLine(2, "JOIN #chan2")
self.getMessages(2)
self.connectClient("baz")
self.sendLine(3, "LIST #chan1,#chan2 >0")
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
self.sendLine(3, "LIST #chan1,#chan2 <1")
self.assertEqual(self._parseChanList(3), set())
class FaketimeListTestCase(_BasedListTestCase): class FaketimeListTestCase(_BasedListTestCase):
faketime = "+1y x30" # for every wall clock second, 1 minute passed for the server faketime = "+1y x30" # for every wall clock second, 1 minute passed for the server

View File

@ -32,6 +32,26 @@ 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")

View File

@ -1,7 +1,10 @@
""" """
`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 (
@ -13,7 +16,7 @@ from irctest.numerics import (
from irctest.patma import ANYSTR, StrRe from irctest.patma import ANYSTR, StrRe
class MonitorTestCase(cases.BaseServerTestCase): class _BaseMonitorTestCase(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")
@ -42,6 +45,8 @@ class MonitorTestCase(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):
@ -295,10 +300,11 @@ class MonitorTestCase(cases.BaseServerTestCase):
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.assertEqual(mononline.command, RPL_MONONLINE) self.assertMessageMatch(
self.assertEqual(len(mononline.params), 2, mononline.params) mononline,
self.assertIn(mononline.params[0], ("bar", "*")) command=RPL_MONONLINE,
self.assertEqual(mononline.params[1].split("!")[0], "qux") params=[StrRe(r"(bar|\*)"), StrRe("qux(!.*)?")],
)
# no numerics for a case change # no numerics for a case change
self.sendLine(2, "NICK QUX") self.sendLine(2, "NICK QUX")
@ -309,7 +315,246 @@ class MonitorTestCase(cases.BaseServerTestCase):
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.assertEqual(monoffline.command, RPL_MONOFFLINE) self.assertMessageMatch(
self.assertEqual(len(monoffline.params), 2, monoffline.params) monoffline,
self.assertIn(monoffline.params[0], ("bar", "*")) command=RPL_MONOFFLINE,
self.assertEqual(monoffline.params[1].split("!")[0], "QUX") params=[StrRe(r"(bar|\*)"), "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

@ -178,6 +178,14 @@ class SaslTestCase(cases.BaseServerTestCase):
), ),
"Anope does not handle split AUTHENTICATE (reported on IRC)", "Anope does not handle split AUTHENTICATE (reported on IRC)",
) )
@cases.xfailIf(
lambda self: (
self.controller.services_controller is not None
and self.controller.services_controller.software_name == "Dlk-Services"
),
"Dlk does not handle split AUTHENTICATE "
"https://github.com/DalekIRC/Dalek-Services/issues/28",
)
def testPlainLarge(self): def testPlainLarge(self):
"""Test the client splits large AUTHENTICATE messages whose payload """Test the client splits large AUTHENTICATE messages whose payload
is not a multiple of 400. is not a multiple of 400.

View File

@ -1,36 +1,21 @@
""" """
`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 from irctest import cases, runner
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 testUtf8Validation(self): def testNonUtf8Filtering(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),
@ -38,3 +23,26 @@ 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

@ -37,8 +37,8 @@ class BaseWhoTestCase:
self.sendLine(1, f"USER {self.username} 0 * :{self.realname}") self.sendLine(1, f"USER {self.username} 0 * :{self.realname}")
if auth: if auth:
self.sendLine(1, "CAP END") self.sendLine(1, "CAP END")
self.getRegistrationMessage(1)
self.skipToWelcome(1) self.skipToWelcome(1)
self.getMessages(1)
self.sendLine(1, "JOIN #chan") self.sendLine(1, "JOIN #chan")
self.getMessages(1) self.getMessages(1)

View File

@ -71,7 +71,10 @@ class _WhoisTestMixin(cases.BaseServerTestCase):
last_message, last_message,
command=RPL_ENDOFWHOIS, command=RPL_ENDOFWHOIS,
params=["nick1", "nick2", ANYSTR], params=["nick1", "nick2", ANYSTR],
fail_msg=f"Last message was not RPL_ENDOFWHOIS ({RPL_ENDOFWHOIS})", fail_msg=(
f"Expected RPL_ENDOFWHOIS ({RPL_ENDOFWHOIS}) as last message, "
f"got {{msg}}"
),
) )
unexpected_messages = [] unexpected_messages = []
@ -96,6 +99,12 @@ 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

@ -98,7 +98,7 @@ class WhowasTestCase(cases.BaseServerTestCase):
"Servers MUST reply with either ERR_WASNOSUCHNICK or [...], "Servers MUST reply with either ERR_WASNOSUCHNICK or [...],
both followed with RPL_ENDOFWHOWAS" both followed with RPL_ENDOFWHOWAS"
-- https://github.com/ircdocs/modern-irc/pull/170 -- https://modern.ircdocs.horse/#whowas-message
""" """
self.connectClient("nick1") self.connectClient("nick1")
@ -210,7 +210,7 @@ class WhowasTestCase(cases.BaseServerTestCase):
"The history is searched backward, returning the most recent entry first." "The history is searched backward, returning the most recent entry first."
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
-- https://github.com/ircdocs/modern-irc/pull/170 -- https://modern.ircdocs.horse/#whowas-message
""" """
self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2") self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2")
@ -224,7 +224,7 @@ class WhowasTestCase(cases.BaseServerTestCase):
"If there are multiple entries, up to <count> replies will be returned" "If there are multiple entries, up to <count> replies will be returned"
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
-- https://github.com/ircdocs/modern-irc/pull/170 -- https://modern.ircdocs.horse/#whowas-message
""" """
self._testWhowasMultiple(second_result=False, whowas_command="WHOWAS nick2 1") self._testWhowasMultiple(second_result=False, whowas_command="WHOWAS nick2 1")
@ -238,7 +238,7 @@ class WhowasTestCase(cases.BaseServerTestCase):
"If there are multiple entries, up to <count> replies will be returned" "If there are multiple entries, up to <count> replies will be returned"
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
-- https://github.com/ircdocs/modern-irc/pull/170 -- https://modern.ircdocs.horse/#whowas-message
""" """
self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 2") self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 2")
@ -253,7 +253,10 @@ class WhowasTestCase(cases.BaseServerTestCase):
is done." is done."
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
-- https://github.com/ircdocs/modern-irc/pull/170
"If given, <count> SHOULD be a positive number. Otherwise, a full search
"is done.
-- https://modern.ircdocs.horse/#whowas-message
""" """
self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 -1") self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 -1")
@ -271,7 +274,10 @@ class WhowasTestCase(cases.BaseServerTestCase):
is done." is done."
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
-- https://github.com/ircdocs/modern-irc/pull/170
"If given, <count> SHOULD be a positive number. Otherwise, a full search
"is done.
-- https://modern.ircdocs.horse/#whowas-message
""" """
self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 0") self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 0")
@ -280,7 +286,7 @@ class WhowasTestCase(cases.BaseServerTestCase):
""" """
"Wildcards are allowed in the <target> parameter." "Wildcards are allowed in the <target> parameter."
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
-- https://github.com/ircdocs/modern-irc/pull/170 -- https://modern.ircdocs.horse/#whowas-message
""" """
if self.controller.software_name == "Bahamut": if self.controller.software_name == "Bahamut":
raise runner.OptionalExtensionNotSupported("WHOWAS mask") raise runner.OptionalExtensionNotSupported("WHOWAS mask")
@ -324,7 +330,7 @@ class WhowasTestCase(cases.BaseServerTestCase):
""" """
"If the `<nick>` argument is missing, they SHOULD send a single reply, using "If the `<nick>` argument is missing, they SHOULD send a single reply, using
either ERR_NONICKNAMEGIVEN or ERR_NEEDMOREPARAMS" either ERR_NONICKNAMEGIVEN or ERR_NEEDMOREPARAMS"
-- https://github.com/ircdocs/modern-irc/pull/170 -- https://modern.ircdocs.horse/#whowas-message
""" """
# But no one seems to follow this. Most implementations use ERR_NEEDMOREPARAMS # But no one seems to follow this. Most implementations use ERR_NEEDMOREPARAMS
# instead of ERR_NONICKNAMEGIVEN; and I couldn't find any that returns # instead of ERR_NONICKNAMEGIVEN; and I couldn't find any that returns
@ -358,7 +364,7 @@ class WhowasTestCase(cases.BaseServerTestCase):
""" """
https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3
https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
-- https://github.com/ircdocs/modern-irc/pull/170 -- https://modern.ircdocs.horse/#whowas-message
and: and:
@ -371,7 +377,7 @@ class WhowasTestCase(cases.BaseServerTestCase):
"Servers MUST reply with either ERR_WASNOSUCHNICK or [...], "Servers MUST reply with either ERR_WASNOSUCHNICK or [...],
both followed with RPL_ENDOFWHOWAS" both followed with RPL_ENDOFWHOWAS"
-- https://github.com/ircdocs/modern-irc/pull/170 -- https://modern.ircdocs.horse/#whowas-message
""" """
self.connectClient("nick1") self.connectClient("nick1")

View File

@ -27,16 +27,19 @@ 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_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
@ -49,6 +52,7 @@ class Capabilities(enum.Enum):
@enum.unique @enum.unique
class IsupportTokens(enum.Enum): class IsupportTokens(enum.Enum):
ACCOUNTEXTBAN = "ACCOUNTEXTBAN"
BOT = "BOT" BOT = "BOT"
ELIST = "ELIST" ELIST = "ELIST"
INVEX = "INVEX" INVEX = "INVEX"
@ -56,6 +60,7 @@ 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

@ -116,7 +116,7 @@ def get_build_job(*, software_config, software_id, version_flavor):
return None return None
return { return {
"runs-on": "ubuntu-latest", "runs-on": "ubuntu-20.04",
"steps": [ "steps": [
{ {
"name": "Create directories", "name": "Create directories",
@ -146,7 +146,7 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
for software_id in test_config.get("software", []): for software_id in test_config.get("software", []):
software_config = config["software"][software_id] software_config = config["software"][software_id]
env += test_config.get("env", {}).get(version_flavor.value, "") + " " env += software_config.get("env", "") + " "
if "prefix" in software_config: if "prefix" in software_config:
env += ( env += (
f"PATH={software_config['prefix']}/sbin" f"PATH={software_config['prefix']}/sbin"
@ -191,7 +191,7 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
unpack = [] unpack = []
return { return {
"runs-on": "ubuntu-latest", "runs-on": "ubuntu-20.04",
"needs": needs, "needs": needs,
"steps": [ "steps": [
{"uses": "actions/checkout@v2"}, {"uses": "actions/checkout@v2"},
@ -263,7 +263,6 @@ 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}
@ -307,7 +306,7 @@ 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-latest", "runs-on": "ubuntu-20.04",
# 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()",

View File

@ -12,6 +12,9 @@ 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,19 +18,23 @@ 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
message-tags message-tags
draft/multiline draft/multiline
multi-prefix multi-prefix
server-time server-time
setname
sts sts
# isupport tokens # isupport tokens
ACCOUNTEXTBAN
BOT BOT
ELIST ELIST
INVEX INVEX
@ -38,6 +42,7 @@ 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

@ -304,6 +304,7 @@ software:
############################# #############################
# Services: # Services:
anope: anope:
name: Anope name: Anope
repository: anope/anope repository: anope/anope
@ -321,6 +322,24 @@ software:
make -C build -j 4 make -C build -j 4
make -C build install make -C build install
dlk:
name: Dlk
repository: DalekIRC/Dalek-Services
separate_build_job: false
path: Dlk-Services
refs:
stable: &dlk_stable "effd18652fc1c847d1959089d9cca9ff9837a8c0"
release: *dlk_stable
devel: "main"
devel_release: *dlk_stable
build_script: |
pip install pifpaf
wget -q https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
wget -q https://wordpress.org/latest.zip -O wordpress-latest.zip
env: >-
IRCTEST_DLK_PATH="${{ github.workspace }}/Dlk-Services"
IRCTEST_WP_CLI_PATH="${{ github.workspace }}/wp-cli.phar"
IRCTEST_WP_ZIP_PATH="${{ github.workspace }}/wordpress-latest.zip"
############################# #############################
@ -425,6 +444,9 @@ tests:
unrealircd-anope: unrealircd-anope:
software: [unrealircd, anope] software: [unrealircd, anope]
unrealircd-dlk:
software: [unrealircd, dlk]
limnoria: limnoria:
software: [limnoria] software: [limnoria]