mirror of
https://github.com/progval/irctest.git
synced 2025-04-06 23:39:46 +00:00
Compare commits
1 Commits
elist-2par
...
noctcp
Author | SHA1 | Date | |
---|---|---|---|
e4bca8a401 |
120
.github/deploy_to_netlify.py
vendored
120
.github/deploy_to_netlify.py
vendored
@ -1,120 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import pprint
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
event_name = os.environ["GITHUB_EVENT_NAME"]
|
|
||||||
|
|
||||||
is_pull_request = is_push = False
|
|
||||||
if event_name.startswith("pull_request"):
|
|
||||||
is_pull_request = True
|
|
||||||
elif event_name.startswith("push"):
|
|
||||||
is_push = True
|
|
||||||
elif event_name.startswith("schedule"):
|
|
||||||
# Don't publish; scheduled workflows run against the latest commit of every
|
|
||||||
# implementation, so they are likely to have failed tests for the wrong reasons
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
print("Unexpected event name:", event_name)
|
|
||||||
|
|
||||||
with open(os.environ["GITHUB_EVENT_PATH"]) as fd:
|
|
||||||
github_event = json.load(fd)
|
|
||||||
|
|
||||||
pprint.pprint(github_event)
|
|
||||||
|
|
||||||
context_suffix = ""
|
|
||||||
|
|
||||||
command = ["netlify", "deploy", "--dir=dashboard/"]
|
|
||||||
if is_pull_request:
|
|
||||||
pr_number = github_event["number"]
|
|
||||||
sha = github_event.get("after") or github_event["pull_request"]["head"]["sha"]
|
|
||||||
# Aliases can't exceed 37 chars
|
|
||||||
command.extend(["--alias", f"pr-{pr_number}-{sha[0:10]}"])
|
|
||||||
context_suffix = " (pull_request)"
|
|
||||||
elif is_push:
|
|
||||||
ref = github_event["ref"]
|
|
||||||
m = re.match("refs/heads/(.*)", ref)
|
|
||||||
if m:
|
|
||||||
branch = m.group(1)
|
|
||||||
sha = github_event["head_commit"]["id"]
|
|
||||||
|
|
||||||
if branch in ("main", "master"):
|
|
||||||
command.extend(["--prod"])
|
|
||||||
else:
|
|
||||||
command.extend(["--alias", f"br-{branch[0:23]}-{sha[0:10]}"])
|
|
||||||
context_suffix = " (push)"
|
|
||||||
else:
|
|
||||||
# TODO
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
print("Running", command)
|
|
||||||
proc = subprocess.run(command, capture_output=True)
|
|
||||||
|
|
||||||
output = proc.stdout.decode()
|
|
||||||
assert proc.returncode == 0, (output, proc.stderr.decode())
|
|
||||||
print(output)
|
|
||||||
|
|
||||||
m = re.search("https://[^ ]*--[^ ]*netlify.app", output)
|
|
||||||
assert m
|
|
||||||
netlify_site_url = m.group(0)
|
|
||||||
target_url = f"{netlify_site_url}/index.xhtml"
|
|
||||||
|
|
||||||
print("Published to", netlify_site_url)
|
|
||||||
|
|
||||||
|
|
||||||
def send_status() -> None:
|
|
||||||
statuses_url = github_event["repository"]["statuses_url"].format(sha=sha)
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"state": "success",
|
|
||||||
"context": f"Dashboard{context_suffix}",
|
|
||||||
"description": "Table of all test results",
|
|
||||||
"target_url": target_url,
|
|
||||||
}
|
|
||||||
request = urllib.request.Request(
|
|
||||||
statuses_url,
|
|
||||||
data=json.dumps(payload).encode(),
|
|
||||||
headers={
|
|
||||||
"Authorization": f'token {os.environ["GITHUB_TOKEN"]}',
|
|
||||||
"Content-Type": "text/json",
|
|
||||||
"Accept": "application/vnd.github+json",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
response = urllib.request.urlopen(request)
|
|
||||||
|
|
||||||
assert response.status == 201, response.read()
|
|
||||||
|
|
||||||
|
|
||||||
send_status()
|
|
||||||
|
|
||||||
|
|
||||||
def send_pr_comment() -> None:
|
|
||||||
comments_url = github_event["pull_request"]["_links"]["comments"]["href"]
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"body": f"[Test results]({target_url})",
|
|
||||||
}
|
|
||||||
request = urllib.request.Request(
|
|
||||||
comments_url,
|
|
||||||
data=json.dumps(payload).encode(),
|
|
||||||
headers={
|
|
||||||
"Authorization": f'token {os.environ["GITHUB_TOKEN"]}',
|
|
||||||
"Content-Type": "text/json",
|
|
||||||
"Accept": "application/vnd.github+json",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
response = urllib.request.urlopen(request)
|
|
||||||
|
|
||||||
assert response.status == 201, response.read()
|
|
||||||
|
|
||||||
|
|
||||||
if is_pull_request:
|
|
||||||
send_pr_comment()
|
|
338
.github/workflows/test-devel.yml
vendored
338
.github/workflows/test-devel.yml
vendored
@ -3,24 +3,20 @@
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-anope:
|
build-anope:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
- name: Create directories
|
- name: Create directories
|
||||||
run: cd ~/; mkdir -p .local/ go/
|
run: cd ~/; mkdir -p .local/ go/
|
||||||
- name: Cache dependencies
|
- name: Cache Anope
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
key: 3-${{ runner.os }}-anope-devel
|
key: 3-${{ runner.os }}-anope-2.0.9
|
||||||
path: '~/.cache
|
path: '~/.cache
|
||||||
|
|
||||||
${ github.workspace }/anope
|
${{ github.workspace }}/anope
|
||||||
|
|
||||||
'
|
'
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set up Python 3.7
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.7
|
|
||||||
- name: Checkout Anope
|
- name: Checkout Anope
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
@ -28,7 +24,7 @@ jobs:
|
|||||||
ref: 2.0.9
|
ref: 2.0.9
|
||||||
repository: anope/anope
|
repository: anope/anope
|
||||||
- name: Build Anope
|
- name: Build Anope
|
||||||
run: |
|
run: |-
|
||||||
cd $GITHUB_WORKSPACE/anope/
|
cd $GITHUB_WORKSPACE/anope/
|
||||||
cp $GITHUB_WORKSPACE/data/anope/* .
|
cp $GITHUB_WORKSPACE/data/anope/* .
|
||||||
CFLAGS=-O0 ./Config -quick
|
CFLAGS=-O0 ./Config -quick
|
||||||
@ -43,7 +39,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -71,7 +67,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd $GITHUB_WORKSPACE/Bahamut/
|
cd $GITHUB_WORKSPACE/Bahamut/
|
||||||
patch src/s_user.c < $GITHUB_WORKSPACE/patches/bahamut_localhost.patch
|
patch src/s_user.c < $GITHUB_WORKSPACE/patches/bahamut_localhost.patch
|
||||||
patch src/s_bsd.c < $GITHUB_WORKSPACE/patches/bahamut_mainloop.patch
|
|
||||||
echo "#undef THROTTLE_ENABLE" >> include/config.h
|
echo "#undef THROTTLE_ENABLE" >> include/config.h
|
||||||
libtoolize --force
|
libtoolize --force
|
||||||
aclocal
|
aclocal
|
||||||
@ -92,7 +87,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -131,7 +126,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -162,7 +157,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -203,7 +198,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -245,7 +240,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -285,7 +280,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -320,8 +315,6 @@ 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
|
||||||
# Prevent download of geoIP database on first startup
|
|
||||||
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
|
||||||
@ -331,7 +324,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-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/
|
||||||
@ -366,8 +359,6 @@ 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
|
||||||
# Prevent download of geoIP database on first startup
|
|
||||||
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
|
||||||
@ -378,7 +369,7 @@ jobs:
|
|||||||
retention-days: 1
|
retention-days: 1
|
||||||
publish-test-results:
|
publish-test-results:
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
name: Publish Dashboard
|
name: Publish Unit Tests Results
|
||||||
needs:
|
needs:
|
||||||
- test-bahamut
|
- test-bahamut
|
||||||
- test-bahamut-anope
|
- test-bahamut-anope
|
||||||
@ -389,7 +380,6 @@ jobs:
|
|||||||
- test-inspircd-anope
|
- test-inspircd-anope
|
||||||
- test-ircu2
|
- test-ircu2
|
||||||
- test-limnoria
|
- test-limnoria
|
||||||
- test-nefarious
|
|
||||||
- test-ngircd
|
- test-ngircd
|
||||||
- test-ngircd-anope
|
- test-ngircd-anope
|
||||||
- test-ngircd-atheme
|
- test-ngircd-atheme
|
||||||
@ -400,35 +390,38 @@ jobs:
|
|||||||
- test-unrealircd-5
|
- test-unrealircd-5
|
||||||
- test-unrealircd-anope
|
- test-unrealircd-anope
|
||||||
- test-unrealircd-atheme
|
- test-unrealircd-atheme
|
||||||
- test-unrealircd-dlk
|
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
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
- name: Install dashboard dependencies
|
- if: github.event_name == 'pull_request'
|
||||||
run: |-
|
name: Publish Unit Test Results
|
||||||
python -m pip install --upgrade pip
|
uses: actions/github-script@v4
|
||||||
pip install defusedxml docutils -r requirements.txt
|
with:
|
||||||
- name: Generate dashboard
|
result-encoding: string
|
||||||
run: |-
|
script: |
|
||||||
shopt -s globstar
|
let body = '';
|
||||||
python3 -m irctest.dashboard.format dashboard/ artifacts/**/*.xml
|
const options = {};
|
||||||
echo '/ /index.xhtml' > dashboard/_redirects
|
options.listeners = {
|
||||||
- name: Install netlify-cli
|
stdout: (data) => {
|
||||||
run: npm i -g netlify-cli
|
body += data.toString();
|
||||||
- env:
|
}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
};
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
await exec.exec('bash', ['-c', 'shopt -s globstar; python3 report.py artifacts/**/*.xml'], options);
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
github.issues.createComment({
|
||||||
name: Deploy to Netlify
|
issue_number: context.issue.number,
|
||||||
run: ./.github/deploy_to_netlify.py
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
return body;
|
||||||
test-bahamut:
|
test-bahamut:
|
||||||
needs:
|
needs:
|
||||||
- build-bahamut
|
- build-bahamut
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -442,8 +435,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -451,18 +444,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
bahamut
|
bahamut
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_bahamut_devel
|
name: pytest results bahamut (devel)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-bahamut-anope:
|
test-bahamut-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-bahamut
|
- build-bahamut
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -481,8 +473,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -490,17 +482,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
bahamut-anope
|
bahamut-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -514,8 +505,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -523,16 +514,15 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
bahamut-atheme
|
bahamut-atheme
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -547,15 +537,15 @@ jobs:
|
|||||||
repository: ergochat/ergo
|
repository: ergochat/ergo
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.19.0
|
go-version: ^1.18.0
|
||||||
- run: go version
|
- run: go version
|
||||||
- name: Build Ergo
|
- name: Build Ergo
|
||||||
run: |
|
run: |
|
||||||
cd $GITHUB_WORKSPACE/ergo/
|
cd $GITHUB_WORKSPACE/ergo/
|
||||||
make build
|
make build
|
||||||
make install
|
make install
|
||||||
- name: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -563,18 +553,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/go/sbin:~/go/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/go/sbin:~/go/bin:$PATH
|
||||||
make ergo
|
make ergo
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_ergo_devel
|
name: pytest results ergo (devel)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-hybrid:
|
test-hybrid:
|
||||||
needs:
|
needs:
|
||||||
- build-hybrid
|
- build-hybrid
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -593,8 +582,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -602,17 +591,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
hybrid
|
hybrid
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -626,8 +614,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -635,18 +623,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
||||||
make inspircd
|
make inspircd
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_inspircd_devel
|
name: pytest results inspircd (devel)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-inspircd-anope:
|
test-inspircd-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-inspircd
|
- build-inspircd
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -665,8 +652,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -674,16 +661,15 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH make
|
||||||
inspircd-anope
|
inspircd-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -704,8 +690,8 @@ jobs:
|
|||||||
./configure --prefix=$HOME/.local/ --with-maxcon=1024 --enable-debug
|
./configure --prefix=$HOME/.local/ --with-maxcon=1024 --enable-debug
|
||||||
make -j 4
|
make -j 4
|
||||||
make install
|
make install
|
||||||
- name: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -713,16 +699,15 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
ircu2
|
ircu2
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -732,8 +717,8 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install git+https://github.com/ProgVal/Limnoria.git@testing cryptography
|
run: pip install git+https://github.com/ProgVal/Limnoria.git@testing cryptography
|
||||||
pyxmpp2-scram
|
pyxmpp2-scram
|
||||||
- name: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -741,55 +726,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
limnoria
|
limnoria
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_limnoria_devel
|
name: pytest results limnoria (devel)
|
||||||
path: pytest.xml
|
|
||||||
test-nefarious:
|
|
||||||
needs: []
|
|
||||||
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: Checkout nefarious
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
path: nefarious
|
|
||||||
ref: master
|
|
||||||
repository: evilnet/nefarious2
|
|
||||||
- name: Build nefarious
|
|
||||||
run: |
|
|
||||||
cd $GITHUB_WORKSPACE/nefarious
|
|
||||||
./configure --prefix=$HOME/.local/ --enable-debug
|
|
||||||
make -j 4
|
|
||||||
make install
|
|
||||||
cp $GITHUB_WORKSPACE/data/nefarious/* $HOME/.local/lib
|
|
||||||
- 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 make
|
|
||||||
nefarious
|
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
|
||||||
name: Publish results
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -803,8 +749,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -812,18 +758,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH
|
||||||
make ngircd
|
make ngircd
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_ngircd_devel
|
name: pytest results ngircd (devel)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-ngircd-anope:
|
test-ngircd-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-ngircd
|
- build-ngircd
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -842,8 +787,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -851,17 +796,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH make
|
||||||
ngircd-anope
|
ngircd-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -875,8 +819,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -884,18 +828,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH
|
||||||
make ngircd-atheme
|
make ngircd-atheme
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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
|
||||||
test-plexus4:
|
test-plexus4:
|
||||||
needs:
|
needs:
|
||||||
- build-plexus4
|
- build-plexus4
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -914,8 +857,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -923,17 +866,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
plexus4
|
plexus4
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -947,8 +889,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -956,16 +898,15 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
solanum
|
solanum
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -974,8 +915,8 @@ jobs:
|
|||||||
python-version: 3.7
|
python-version: 3.7
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install git+https://github.com/sopel-irc/sopel.git
|
run: pip install git+https://github.com/sopel-irc/sopel.git
|
||||||
- name: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -983,17 +924,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
sopel
|
sopel
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1007,8 +947,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1016,17 +956,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
||||||
make unrealircd
|
make unrealircd
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1040,8 +979,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1049,18 +988,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
||||||
make unrealircd-5
|
make unrealircd-5
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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
|
||||||
test-unrealircd-anope:
|
test-unrealircd-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-unrealircd
|
- build-unrealircd
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1079,8 +1017,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1088,17 +1026,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH make
|
||||||
unrealircd-anope
|
unrealircd-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1112,8 +1049,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1121,58 +1058,11 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
||||||
make unrealircd-atheme
|
make unrealircd-atheme
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_unrealircd-atheme_devel
|
name: pytest results unrealircd-atheme (devel)
|
||||||
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
|
path: pytest.xml
|
||||||
name: irctest with devel versions
|
name: irctest with devel versions
|
||||||
'on':
|
'on':
|
||||||
|
87
.github/workflows/test-devel_release.yml
vendored
87
.github/workflows/test-devel_release.yml
vendored
@ -3,24 +3,20 @@
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-anope:
|
build-anope:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
- name: Create directories
|
- name: Create directories
|
||||||
run: cd ~/; mkdir -p .local/ go/
|
run: cd ~/; mkdir -p .local/ go/
|
||||||
- name: Cache dependencies
|
- name: Cache Anope
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
key: 3-${{ runner.os }}-anope-devel_release
|
key: 3-${{ runner.os }}-anope-2.0.9
|
||||||
path: '~/.cache
|
path: '~/.cache
|
||||||
|
|
||||||
${ github.workspace }/anope
|
${{ github.workspace }}/anope
|
||||||
|
|
||||||
'
|
'
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set up Python 3.7
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.7
|
|
||||||
- name: Checkout Anope
|
- name: Checkout Anope
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
@ -28,7 +24,7 @@ jobs:
|
|||||||
ref: 2.0.9
|
ref: 2.0.9
|
||||||
repository: anope/anope
|
repository: anope/anope
|
||||||
- name: Build Anope
|
- name: Build Anope
|
||||||
run: |
|
run: |-
|
||||||
cd $GITHUB_WORKSPACE/anope/
|
cd $GITHUB_WORKSPACE/anope/
|
||||||
cp $GITHUB_WORKSPACE/data/anope/* .
|
cp $GITHUB_WORKSPACE/data/anope/* .
|
||||||
CFLAGS=-O0 ./Config -quick
|
CFLAGS=-O0 ./Config -quick
|
||||||
@ -43,7 +39,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -75,39 +71,43 @@ jobs:
|
|||||||
retention-days: 1
|
retention-days: 1
|
||||||
publish-test-results:
|
publish-test-results:
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
name: Publish Dashboard
|
name: Publish Unit Tests Results
|
||||||
needs:
|
needs:
|
||||||
- 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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Download Artifacts
|
- name: Download Artifacts
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
- name: Install dashboard dependencies
|
- if: github.event_name == 'pull_request'
|
||||||
run: |-
|
name: Publish Unit Test Results
|
||||||
python -m pip install --upgrade pip
|
uses: actions/github-script@v4
|
||||||
pip install defusedxml docutils -r requirements.txt
|
with:
|
||||||
- name: Generate dashboard
|
result-encoding: string
|
||||||
run: |-
|
script: |
|
||||||
shopt -s globstar
|
let body = '';
|
||||||
python3 -m irctest.dashboard.format dashboard/ artifacts/**/*.xml
|
const options = {};
|
||||||
echo '/ /index.xhtml' > dashboard/_redirects
|
options.listeners = {
|
||||||
- name: Install netlify-cli
|
stdout: (data) => {
|
||||||
run: npm i -g netlify-cli
|
body += data.toString();
|
||||||
- env:
|
}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
};
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
await exec.exec('bash', ['-c', 'shopt -s globstar; python3 report.py artifacts/**/*.xml'], options);
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
github.issues.createComment({
|
||||||
name: Deploy to Netlify
|
issue_number: context.issue.number,
|
||||||
run: ./.github/deploy_to_netlify.py
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
return body;
|
||||||
test-inspircd:
|
test-inspircd:
|
||||||
needs:
|
needs:
|
||||||
- build-inspircd
|
- build-inspircd
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -121,8 +121,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -130,18 +130,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
||||||
make inspircd
|
make inspircd
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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
|
||||||
test-inspircd-anope:
|
test-inspircd-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-inspircd
|
- build-inspircd
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -160,8 +159,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -169,17 +168,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH make
|
||||||
inspircd-anope
|
inspircd-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -193,8 +191,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -202,12 +200,11 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
||||||
make inspircd-atheme
|
make inspircd-atheme
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
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
|
||||||
name: irctest with devel_release versions
|
name: irctest with devel_release versions
|
||||||
'on':
|
'on':
|
||||||
|
416
.github/workflows/test-stable.yml
vendored
416
.github/workflows/test-stable.yml
vendored
@ -3,24 +3,20 @@
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-anope:
|
build-anope:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
- name: Create directories
|
- name: Create directories
|
||||||
run: cd ~/; mkdir -p .local/ go/
|
run: cd ~/; mkdir -p .local/ go/
|
||||||
- name: Cache dependencies
|
- name: Cache Anope
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
key: 3-${{ runner.os }}-anope-stable
|
key: 3-${{ runner.os }}-anope-2.0.9
|
||||||
path: '~/.cache
|
path: '~/.cache
|
||||||
|
|
||||||
${ github.workspace }/anope
|
${{ github.workspace }}/anope
|
||||||
|
|
||||||
'
|
'
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set up Python 3.7
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.7
|
|
||||||
- name: Checkout Anope
|
- name: Checkout Anope
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
@ -28,7 +24,7 @@ jobs:
|
|||||||
ref: 2.0.9
|
ref: 2.0.9
|
||||||
repository: anope/anope
|
repository: anope/anope
|
||||||
- name: Build Anope
|
- name: Build Anope
|
||||||
run: |
|
run: |-
|
||||||
cd $GITHUB_WORKSPACE/anope/
|
cd $GITHUB_WORKSPACE/anope/
|
||||||
cp $GITHUB_WORKSPACE/data/anope/* .
|
cp $GITHUB_WORKSPACE/data/anope/* .
|
||||||
CFLAGS=-O0 ./Config -quick
|
CFLAGS=-O0 ./Config -quick
|
||||||
@ -43,7 +39,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -71,7 +67,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd $GITHUB_WORKSPACE/Bahamut/
|
cd $GITHUB_WORKSPACE/Bahamut/
|
||||||
patch src/s_user.c < $GITHUB_WORKSPACE/patches/bahamut_localhost.patch
|
patch src/s_user.c < $GITHUB_WORKSPACE/patches/bahamut_localhost.patch
|
||||||
patch src/s_bsd.c < $GITHUB_WORKSPACE/patches/bahamut_mainloop.patch
|
|
||||||
echo "#undef THROTTLE_ENABLE" >> include/config.h
|
echo "#undef THROTTLE_ENABLE" >> include/config.h
|
||||||
libtoolize --force
|
libtoolize --force
|
||||||
aclocal
|
aclocal
|
||||||
@ -92,7 +87,7 @@ jobs:
|
|||||||
path: ~/artefacts-*.tar.gz
|
path: ~/artefacts-*.tar.gz
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
build-charybdis:
|
build-charybdis:
|
||||||
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/
|
||||||
@ -132,7 +127,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -171,7 +166,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -202,7 +197,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -243,7 +238,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -285,7 +280,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -325,7 +320,7 @@ jobs:
|
|||||||
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/
|
||||||
@ -347,7 +342,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
path: unrealircd
|
path: unrealircd
|
||||||
ref: cedd23ae9cdd5985ce16e9869cbdb808479c3fc4
|
ref: daa0c11f285c7123ba9fa2966dee2d1a17729f1e
|
||||||
repository: unrealircd/unrealircd
|
repository: unrealircd/unrealircd
|
||||||
- name: Build UnrealIRCd 6
|
- name: Build UnrealIRCd 6
|
||||||
run: |
|
run: |
|
||||||
@ -360,8 +355,6 @@ 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
|
||||||
# Prevent download of geoIP database on first startup
|
|
||||||
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
|
||||||
@ -371,7 +364,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-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/
|
||||||
@ -406,8 +399,6 @@ 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
|
||||||
# Prevent download of geoIP database on first startup
|
|
||||||
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
|
||||||
@ -418,7 +409,7 @@ jobs:
|
|||||||
retention-days: 1
|
retention-days: 1
|
||||||
publish-test-results:
|
publish-test-results:
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
name: Publish Dashboard
|
name: Publish Unit Tests Results
|
||||||
needs:
|
needs:
|
||||||
- test-bahamut
|
- test-bahamut
|
||||||
- test-bahamut-anope
|
- test-bahamut-anope
|
||||||
@ -432,7 +423,6 @@ jobs:
|
|||||||
- test-irc2
|
- test-irc2
|
||||||
- test-ircu2
|
- test-ircu2
|
||||||
- test-limnoria
|
- test-limnoria
|
||||||
- test-nefarious
|
|
||||||
- test-ngircd
|
- test-ngircd
|
||||||
- test-ngircd-anope
|
- test-ngircd-anope
|
||||||
- test-ngircd-atheme
|
- test-ngircd-atheme
|
||||||
@ -443,35 +433,38 @@ jobs:
|
|||||||
- test-unrealircd-5
|
- test-unrealircd-5
|
||||||
- test-unrealircd-anope
|
- test-unrealircd-anope
|
||||||
- test-unrealircd-atheme
|
- test-unrealircd-atheme
|
||||||
- test-unrealircd-dlk
|
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
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
- name: Install dashboard dependencies
|
- if: github.event_name == 'pull_request'
|
||||||
run: |-
|
name: Publish Unit Test Results
|
||||||
python -m pip install --upgrade pip
|
uses: actions/github-script@v4
|
||||||
pip install defusedxml docutils -r requirements.txt
|
with:
|
||||||
- name: Generate dashboard
|
result-encoding: string
|
||||||
run: |-
|
script: |
|
||||||
shopt -s globstar
|
let body = '';
|
||||||
python3 -m irctest.dashboard.format dashboard/ artifacts/**/*.xml
|
const options = {};
|
||||||
echo '/ /index.xhtml' > dashboard/_redirects
|
options.listeners = {
|
||||||
- name: Install netlify-cli
|
stdout: (data) => {
|
||||||
run: npm i -g netlify-cli
|
body += data.toString();
|
||||||
- env:
|
}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
};
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
await exec.exec('bash', ['-c', 'shopt -s globstar; python3 report.py artifacts/**/*.xml'], options);
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
github.issues.createComment({
|
||||||
name: Deploy to Netlify
|
issue_number: context.issue.number,
|
||||||
run: ./.github/deploy_to_netlify.py
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
return body;
|
||||||
test-bahamut:
|
test-bahamut:
|
||||||
needs:
|
needs:
|
||||||
- build-bahamut
|
- build-bahamut
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -485,8 +478,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -494,18 +487,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
bahamut
|
bahamut
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_bahamut_stable
|
name: pytest results bahamut (stable)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-bahamut-anope:
|
test-bahamut-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-bahamut
|
- build-bahamut
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -524,8 +516,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -533,17 +525,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
bahamut-anope
|
bahamut-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_bahamut-anope_stable
|
name: pytest results bahamut-anope (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -557,8 +548,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -566,17 +557,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
bahamut-atheme
|
bahamut-atheme
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_bahamut-atheme_stable
|
name: pytest results bahamut-atheme (stable)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-charybdis:
|
test-charybdis:
|
||||||
needs:
|
needs:
|
||||||
- build-charybdis
|
- build-charybdis
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -590,8 +580,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -599,16 +589,15 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
charybdis
|
charybdis
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_charybdis_stable
|
name: pytest results charybdis (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -623,15 +612,15 @@ jobs:
|
|||||||
repository: ergochat/ergo
|
repository: ergochat/ergo
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.19.0
|
go-version: ^1.18.0
|
||||||
- run: go version
|
- run: go version
|
||||||
- name: Build Ergo
|
- name: Build Ergo
|
||||||
run: |
|
run: |
|
||||||
cd $GITHUB_WORKSPACE/ergo/
|
cd $GITHUB_WORKSPACE/ergo/
|
||||||
make build
|
make build
|
||||||
make install
|
make install
|
||||||
- name: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -639,18 +628,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/go/sbin:~/go/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/go/sbin:~/go/bin:$PATH
|
||||||
make ergo
|
make ergo
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_ergo_stable
|
name: pytest results ergo (stable)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-hybrid:
|
test-hybrid:
|
||||||
needs:
|
needs:
|
||||||
- build-hybrid
|
- build-hybrid
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -669,8 +657,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -678,17 +666,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
hybrid
|
hybrid
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_hybrid_stable
|
name: pytest results hybrid (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -702,8 +689,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -711,18 +698,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
||||||
make inspircd
|
make inspircd
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_inspircd_stable
|
name: pytest results inspircd (stable)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-inspircd-anope:
|
test-inspircd-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-inspircd
|
- build-inspircd
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -741,8 +727,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -750,17 +736,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH make
|
||||||
inspircd-anope
|
inspircd-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_inspircd-anope_stable
|
name: pytest results inspircd-anope (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -774,8 +759,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -783,49 +768,53 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/inspircd/sbin:~/.local/inspircd/bin:$PATH
|
||||||
make inspircd-atheme
|
make inspircd-atheme
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_inspircd-atheme_stable
|
name: pytest results inspircd-atheme (stable)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-irc2:
|
test-irc2:
|
||||||
needs: []
|
needs: []
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.7
|
python-version: 3.7
|
||||||
- name: Checkout irc2
|
- name: Get source code
|
||||||
uses: actions/checkout@v2
|
run: curl http://ftp.irc.org/ftp/irc/server/irc2.11.2p3.tgz | tar -zx
|
||||||
with:
|
- name: Configure
|
||||||
path: irc2.11.2p3
|
run: 'cd $GITHUB_WORKSPACE/irc2.11.2p3
|
||||||
ref: 59649f24c3a5c27bad5648b48774f27475bccfd3
|
|
||||||
repository: irc-archive/irc2-mirror
|
./configure --prefix=$HOME/.local/
|
||||||
- name: Build irc2
|
|
||||||
run: |
|
cd x86*
|
||||||
# Configure
|
|
||||||
cd $GITHUB_WORKSPACE/irc2.11.2p3
|
echo "#define CMDLINE_CONFIG/" >> config.h
|
||||||
./configure --prefix=$HOME/.local/
|
|
||||||
cd x86*
|
echo "#define DEFAULT_SPLIT_USERS 0" >> config.h
|
||||||
echo "#define CMDLINE_CONFIG/" >> config.h
|
|
||||||
echo "#define DEFAULT_SPLIT_USERS 0" >> config.h
|
echo "#define DEFAULT_SPLIT_SERVERS 0" >> config.h
|
||||||
echo "#define DEFAULT_SPLIT_SERVERS 0" >> config.h
|
|
||||||
#echo "#undef LIST_ALIS_NOTE" >> config.h
|
#echo "#undef LIST_ALIS_NOTE" >> config.h
|
||||||
# TODO: find a better way to make it not fork...
|
|
||||||
echo "#define fork() (0)" >> config.h
|
# TODO: find a better way to make it not fork...
|
||||||
|
|
||||||
|
echo "#define fork() (0)" >> config.h'
|
||||||
|
- name: Compile and install
|
||||||
|
run: 'cd $GITHUB_WORKSPACE/irc2.11.2p3/x86*
|
||||||
|
|
||||||
# Compile and install
|
|
||||||
cd $GITHUB_WORKSPACE/irc2.11.2p3/x86*
|
|
||||||
make -j 4 all
|
make -j 4 all
|
||||||
|
|
||||||
make install
|
make install
|
||||||
|
|
||||||
mkdir -p $HOME/.local/bin
|
mkdir -p $HOME/.local/bin
|
||||||
cp $HOME/.local/sbin/ircd $HOME/.local/bin/ircd
|
|
||||||
- name: Install system dependencies
|
cp $HOME/.local/sbin/ircd $HOME/.local/bin/ircd'
|
||||||
run: sudo apt-get install atheme-services faketime
|
- name: Install Atheme
|
||||||
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -833,16 +822,15 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
irc2
|
irc2
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_irc2_stable
|
name: pytest results irc2 (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -863,8 +851,8 @@ jobs:
|
|||||||
./configure --prefix=$HOME/.local/ --with-maxcon=1024 --enable-debug
|
./configure --prefix=$HOME/.local/ --with-maxcon=1024 --enable-debug
|
||||||
make -j 4
|
make -j 4
|
||||||
make install
|
make install
|
||||||
- name: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -872,16 +860,15 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
ircu2
|
ircu2
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_ircu2_stable
|
name: pytest results ircu2 (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -890,8 +877,8 @@ jobs:
|
|||||||
python-version: 3.7
|
python-version: 3.7
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install limnoria==2022.03.17 cryptography pyxmpp2-scram
|
run: pip install limnoria==2022.03.17 cryptography pyxmpp2-scram
|
||||||
- name: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -899,55 +886,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
limnoria
|
limnoria
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_limnoria_stable
|
name: pytest results limnoria (stable)
|
||||||
path: pytest.xml
|
|
||||||
test-nefarious:
|
|
||||||
needs: []
|
|
||||||
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: Checkout nefarious
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
path: nefarious
|
|
||||||
ref: 985704168ecada12d9e53b46df6087ef9d9fb40b
|
|
||||||
repository: evilnet/nefarious2
|
|
||||||
- name: Build nefarious
|
|
||||||
run: |
|
|
||||||
cd $GITHUB_WORKSPACE/nefarious
|
|
||||||
./configure --prefix=$HOME/.local/ --enable-debug
|
|
||||||
make -j 4
|
|
||||||
make install
|
|
||||||
cp $GITHUB_WORKSPACE/data/nefarious/* $HOME/.local/lib
|
|
||||||
- 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 make
|
|
||||||
nefarious
|
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
|
||||||
name: Publish results
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: pytest-results_nefarious_stable
|
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -961,8 +909,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -970,18 +918,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH
|
||||||
make ngircd
|
make ngircd
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_ngircd_stable
|
name: pytest results ngircd (stable)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-ngircd-anope:
|
test-ngircd-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-ngircd
|
- build-ngircd
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1000,8 +947,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1009,17 +956,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH make
|
||||||
ngircd-anope
|
ngircd-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_ngircd-anope_stable
|
name: pytest results ngircd-anope (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1033,8 +979,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1042,18 +988,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local//sbin:~/.local//bin:$PATH
|
||||||
make ngircd-atheme
|
make ngircd-atheme
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_ngircd-atheme_stable
|
name: pytest results ngircd-atheme (stable)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-plexus4:
|
test-plexus4:
|
||||||
needs:
|
needs:
|
||||||
- build-plexus4
|
- build-plexus4
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1072,8 +1017,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1081,17 +1026,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
plexus4
|
plexus4
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_plexus4_stable
|
name: pytest results plexus4 (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1105,8 +1049,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1114,16 +1058,15 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
solanum
|
solanum
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_solanum_stable
|
name: pytest results solanum (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1132,8 +1075,8 @@ jobs:
|
|||||||
python-version: 3.7
|
python-version: 3.7
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install sopel==7.1.8
|
run: pip install sopel==7.1.8
|
||||||
- name: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1141,17 +1084,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH make
|
||||||
sopel
|
sopel
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_sopel_stable
|
name: pytest results sopel (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1165,8 +1107,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1174,17 +1116,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
||||||
make unrealircd
|
make unrealircd
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_unrealircd_stable
|
name: pytest results unrealircd (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1198,8 +1139,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1207,18 +1148,17 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
||||||
make unrealircd-5
|
make unrealircd-5
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_unrealircd-5_stable
|
name: pytest results unrealircd-5 (stable)
|
||||||
path: pytest.xml
|
path: pytest.xml
|
||||||
test-unrealircd-anope:
|
test-unrealircd-anope:
|
||||||
needs:
|
needs:
|
||||||
- build-unrealircd
|
- build-unrealircd
|
||||||
- build-anope
|
- build-anope
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1237,8 +1177,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1246,17 +1186,16 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH make
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH make
|
||||||
unrealircd-anope
|
unrealircd-anope
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_unrealircd-anope_stable
|
name: pytest results unrealircd-anope (stable)
|
||||||
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@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@ -1270,8 +1209,8 @@ jobs:
|
|||||||
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: Install system dependencies
|
- name: Install Atheme
|
||||||
run: sudo apt-get install atheme-services faketime
|
run: sudo apt-get install atheme-services
|
||||||
- name: Install irctest dependencies
|
- name: Install irctest dependencies
|
||||||
run: |-
|
run: |-
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
@ -1279,58 +1218,11 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
run: PYTEST_ARGS='--junit-xml pytest.xml' PATH=$HOME/.local/bin:$PATH PATH=~/.local/unrealircd/sbin:~/.local/unrealircd/bin:$PATH
|
||||||
make unrealircd-atheme
|
make unrealircd-atheme
|
||||||
timeout-minutes: 30
|
|
||||||
- if: always()
|
- if: always()
|
||||||
name: Publish results
|
name: Publish results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: pytest-results_unrealircd-atheme_stable
|
name: pytest results unrealircd-atheme (stable)
|
||||||
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
|
path: pytest.xml
|
||||||
name: irctest with stable versions
|
name: irctest with stable versions
|
||||||
'on':
|
'on':
|
||||||
|
@ -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: 20.8b1
|
||||||
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: 3.8.3
|
||||||
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]
|
|
||||||
|
144
Makefile
144
Makefile
@ -7,47 +7,88 @@ PYTEST_ARGS ?=
|
|||||||
# Will be appended at the end of the -k argument to pytest
|
# Will be appended at the end of the -k argument to pytest
|
||||||
EXTRA_SELECTORS ?=
|
EXTRA_SELECTORS ?=
|
||||||
|
|
||||||
|
# testPlainLarge fails because it doesn't handle split AUTHENTICATE (reported on IRC)
|
||||||
|
ANOPE_SELECTORS := \
|
||||||
|
and not testPlainLarge
|
||||||
|
|
||||||
|
# buffering tests cannot pass because of issues with UTF-8 handling: https://github.com/DALnet/bahamut/issues/196
|
||||||
|
# mask tests in test_who.py fail because they are not implemented.
|
||||||
|
# some HelpTestCase::*[HELP] tests fail because Bahamut forwards /HELP to HelpServ (but not /HELPOP)
|
||||||
|
# testWhowasMultiTarget fails because Bahamut returns the results in query order instead of chronological order
|
||||||
BAHAMUT_SELECTORS := \
|
BAHAMUT_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
and not IRCv3 \
|
and not IRCv3 \
|
||||||
|
and not buffering \
|
||||||
|
and not (testWho and not whois and mask) \
|
||||||
|
and not testWhoStar \
|
||||||
|
and (not HelpTestCase or HELPOP) \
|
||||||
|
and not testWhowasMultiTarget \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testQuitErrors is very flaky
|
||||||
|
# AccountTagTestCase.testInvite fails because https://github.com/solanum-ircd/solanum/issues/166
|
||||||
|
# testKickDefaultComment fails because it uses the nick of the kickee rather than the kicker.
|
||||||
|
# testWhoisNumerics[oper] fails because charybdis uses RPL_WHOISSPECIAL instead of RPL_WHOISOPERATOR
|
||||||
|
# testWhowasNoSuchNick fails because of a typo (solved in https://github.com/solanum-ircd/solanum/commit/08b7b6bd7e60a760ad47b58cbe8075b45d66166f)
|
||||||
CHARYBDIS_SELECTORS := \
|
CHARYBDIS_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
|
and not testQuitErrors \
|
||||||
|
and not testKickDefaultComment \
|
||||||
|
and not (AccountTagTestCase and testInvite) \
|
||||||
|
and not (testWhoisNumerics and oper) \
|
||||||
|
and not testWhowasNoSuchNick \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testInfoNosuchserver does not apply to Ergo: Ergo ignores the optional <target> argument
|
||||||
ERGO_SELECTORS := \
|
ERGO_SELECTORS := \
|
||||||
not deprecated \
|
not deprecated \
|
||||||
|
and not testInfoNosuchserver \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testInviteUnopped is the only strict test that Hybrid fails
|
||||||
HYBRID_SELECTORS := \
|
HYBRID_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
|
and not testInviteUnopped \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testBotPrivateMessage and testBotChannelMessage fail because https://github.com/inspircd/inspircd/pull/1910 is not released yet
|
||||||
|
# WHOWAS tests fail because https://github.com/inspircd/inspircd/pull/1967 and https://github.com/inspircd/inspircd/pull/1968 are not released yet
|
||||||
INSPIRCD_SELECTORS := \
|
INSPIRCD_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
|
and not testNoticeNonexistentChannel \
|
||||||
|
and not testBotPrivateMessage and not testBotChannelMessage \
|
||||||
|
and not whowas \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# buffering tests fail because ircu2 discards the whole buffer on long lines (TODO: refine how we exclude these tests)
|
||||||
|
# testQuit and testQuitErrors fail because ircu2 does not send ERROR or QUIT
|
||||||
|
# lusers "full" tests fail because they depend on Modern behavior, not just RFC2812
|
||||||
|
# statusmsg tests fail because STATUSMSG is present in ISUPPORT, but it not actually supported as PRIVMSG target
|
||||||
|
# testKeyValidation[empty] fails because ircu2 returns ERR_NEEDMOREPARAMS on empty keys: https://github.com/UndernetIRC/ircu2/issues/13
|
||||||
|
# testKickDefaultComment fails because it uses the nick of the kickee rather than the kicker.
|
||||||
|
# testEmptyRealname fails because it uses a default value instead of ERR_NEEDMOREPARAMS.
|
||||||
# HelpTestCase fails because it returns NOTICEs instead of numerics
|
# HelpTestCase fails because it returns NOTICEs instead of numerics
|
||||||
|
# testWhowasCountZero fails: https://github.com/UndernetIRC/ircu2/pull/19
|
||||||
IRCU2_SELECTORS := \
|
IRCU2_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
$(EXTRA_SELECTORS)
|
and not buffering \
|
||||||
|
and not testQuit \
|
||||||
# same justification as ircu2
|
and not (lusers and full) \
|
||||||
# lusers "unregistered" tests fail because
|
and not statusmsg \
|
||||||
NEFARIOUS_SELECTORS := \
|
and not (testKeyValidation and empty) \
|
||||||
not Ergo \
|
and not testKickDefaultComment \
|
||||||
and not deprecated \
|
and not testEmptyRealname \
|
||||||
and not strict \
|
and not HelpTestCase \
|
||||||
|
and not testWhowasCountZero \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
# same justification as ircu2
|
# same justification as ircu2
|
||||||
@ -55,12 +96,24 @@ SNIRCD_SELECTORS := \
|
|||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
|
and not buffering \
|
||||||
|
and not testQuit \
|
||||||
|
and not (lusers and full) \
|
||||||
|
and not statusmsg \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testListEmpty and testListOne fails because irc2 deprecated LIST
|
||||||
|
# testKickDefaultComment fails because it uses the nick of the kickee rather than the kicker.
|
||||||
|
# testWallopsPrivileges fails because it ignores the command instead of replying ERR_UNKNOWNCOMMAND
|
||||||
|
# HelpTestCase fails because it returns NOTICEs instead of numerics
|
||||||
IRC2_SELECTORS := \
|
IRC2_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
|
and not testListEmpty and not testListOne \
|
||||||
|
and not testKickDefaultComment \
|
||||||
|
and not testWallopsPrivileges \
|
||||||
|
and not HelpTestCase \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
MAMMON_SELECTORS := \
|
MAMMON_SELECTORS := \
|
||||||
@ -69,14 +122,28 @@ MAMMON_SELECTORS := \
|
|||||||
and not strict \
|
and not strict \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testKeyValidation[spaces] and testKeyValidation[empty] fail because ngIRCd does not validate them https://github.com/ngircd/ngircd/issues/290
|
||||||
|
# testStarNick: wat
|
||||||
|
# testEmptyRealname fails because it uses a default value instead of ERR_NEEDMOREPARAMS.
|
||||||
|
# chathistory tests fail because they need nicks longer than 9 chars
|
||||||
|
# HelpTestCase::*[HELP] fails because it returns NOTICEs instead of numerics
|
||||||
NGIRCD_SELECTORS := \
|
NGIRCD_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
|
and not (testKeyValidation and (spaces or empty)) \
|
||||||
|
and not testStarNick \
|
||||||
|
and not testEmptyRealname \
|
||||||
|
and not chathistory \
|
||||||
|
and (not HelpTestCase or HELPOP) \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testInviteUnopped is the only strict test that Plexus4 fails
|
||||||
|
# testInviteInviteOnly fails because Plexus4 allows non-op to invite if (and only if) the channel is not invite-only
|
||||||
PLEXUS4_SELECTORS := \
|
PLEXUS4_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
|
and not testInviteUnopped \
|
||||||
|
and not testInviteInviteOnly \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
@ -87,33 +154,51 @@ LIMNORIA_SELECTORS := \
|
|||||||
(foo or not foo) \
|
(foo or not foo) \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testQuitErrors is too flaky for CI
|
||||||
|
# testKickDefaultComment fails because solanum uses the nick of the kickee rather than the kicker.
|
||||||
SOLANUM_SELECTORS := \
|
SOLANUM_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
|
and not testQuitErrors \
|
||||||
|
and not testKickDefaultComment \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
# Same as Limnoria
|
|
||||||
SOPEL_SELECTORS := \
|
SOPEL_SELECTORS := \
|
||||||
(foo or not foo) \
|
not testPlainNotAvailable \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
|
# testNoticeNonexistentChannel fails: https://bugs.unrealircd.org/view.php?id=5949
|
||||||
|
# regressions::testTagCap fails: https://bugs.unrealircd.org/view.php?id=5948
|
||||||
|
# messages::testLineTooLong fails: https://bugs.unrealircd.org/view.php?id=5947
|
||||||
|
# testCapRemovalByClient and testNakWhole fail pending https://github.com/unrealircd/unrealircd/pull/148
|
||||||
# Tests marked with arbitrary_client_tags can't pass because Unreal whitelists which tags it relays
|
# Tests marked with arbitrary_client_tags can't pass because Unreal whitelists which tags it relays
|
||||||
# Tests marked with react_tag can't pass because Unreal blocks +draft/react https://github.com/unrealircd/unrealircd/pull/149
|
# Tests marked with react_tag can't pass because Unreal blocks +draft/react https://github.com/unrealircd/unrealircd/pull/149
|
||||||
# Tests marked with private_chathistory can't pass because Unreal does not implement CHATHISTORY for DMs
|
# Tests marked with private_chathistory can't pass because Unreal does not implement CHATHISTORY for DMs
|
||||||
|
# testChathistory[BETWEEN] fails: https://bugs.unrealircd.org/view.php?id=5952
|
||||||
|
# testChathistory[AROUND] fails: https://bugs.unrealircd.org/view.php?id=5953
|
||||||
|
# testWhoAllOpers fails because Unreal skips results when the mask is too broad
|
||||||
|
# HELP and HELPOP tests fail because Unreal uses custom numerics https://github.com/unrealircd/unrealircd/pull/184
|
||||||
UNREALIRCD_SELECTORS := \
|
UNREALIRCD_SELECTORS := \
|
||||||
not Ergo \
|
not Ergo \
|
||||||
and not deprecated \
|
and not deprecated \
|
||||||
and not strict \
|
and not strict \
|
||||||
|
and not testNoticeNonexistentChannel \
|
||||||
|
and not (regressions.py and testTagCap) \
|
||||||
|
and not (messages.py and testLineTooLong) \
|
||||||
|
and not (cap.py and (testCapRemovalByClient or testNakWhole)) \
|
||||||
|
and not (account_tag.py and testInvite) \
|
||||||
and not arbitrary_client_tags \
|
and not arbitrary_client_tags \
|
||||||
and not react_tag \
|
and not react_tag \
|
||||||
and not private_chathistory \
|
and not private_chathistory \
|
||||||
|
and not (testChathistory and (between or around)) \
|
||||||
|
and not testWhoAllOpers \
|
||||||
|
and not HelpTestCase \
|
||||||
$(EXTRA_SELECTORS)
|
$(EXTRA_SELECTORS)
|
||||||
|
|
||||||
.PHONY: all flakes bahamut charybdis ergo inspircd ircu2 snircd irc2 mammon nefarious limnoria sopel solanum unrealircd
|
.PHONY: all flakes bahamut charybdis ergo inspircd ircu2 snircd irc2 mammon limnoria sopel solanum unrealircd
|
||||||
|
|
||||||
all: flakes bahamut charybdis ergo inspircd ircu2 snircd irc2 mammon nefarious limnoria sopel solanum unrealircd
|
all: flakes bahamut charybdis ergo inspircd ircu2 snircd irc2 mammon limnoria sopel solanum unrealircd
|
||||||
|
|
||||||
flakes:
|
flakes:
|
||||||
find irctest/ -name "*.py" -not -path "irctest/scram/*" -print0 | xargs -0 pyflakes3
|
find irctest/ -name "*.py" -not -path "irctest/scram/*" -print0 | xargs -0 pyflakes3
|
||||||
@ -122,8 +207,7 @@ bahamut:
|
|||||||
$(PYTEST) $(PYTEST_ARGS) \
|
$(PYTEST) $(PYTEST_ARGS) \
|
||||||
--controller=irctest.controllers.bahamut \
|
--controller=irctest.controllers.bahamut \
|
||||||
-m 'not services' \
|
-m 'not services' \
|
||||||
-n 4 \
|
-n 10 \
|
||||||
-vv -s \
|
|
||||||
-k '$(BAHAMUT_SELECTORS)'
|
-k '$(BAHAMUT_SELECTORS)'
|
||||||
|
|
||||||
bahamut-atheme:
|
bahamut-atheme:
|
||||||
@ -131,6 +215,7 @@ bahamut-atheme:
|
|||||||
--controller=irctest.controllers.bahamut \
|
--controller=irctest.controllers.bahamut \
|
||||||
--services-controller=irctest.controllers.atheme_services \
|
--services-controller=irctest.controllers.atheme_services \
|
||||||
-m 'services' \
|
-m 'services' \
|
||||||
|
-n 10 \
|
||||||
-k '$(BAHAMUT_SELECTORS)'
|
-k '$(BAHAMUT_SELECTORS)'
|
||||||
|
|
||||||
bahamut-anope:
|
bahamut-anope:
|
||||||
@ -138,7 +223,8 @@ bahamut-anope:
|
|||||||
--controller=irctest.controllers.bahamut \
|
--controller=irctest.controllers.bahamut \
|
||||||
--services-controller=irctest.controllers.anope_services \
|
--services-controller=irctest.controllers.anope_services \
|
||||||
-m 'services' \
|
-m 'services' \
|
||||||
-k '$(BAHAMUT_SELECTORS)'
|
-n 10 \
|
||||||
|
-k '$(BAHAMUT_SELECTORS) $(ANOPE_SELECTORS)'
|
||||||
|
|
||||||
charybdis:
|
charybdis:
|
||||||
$(PYTEST) $(PYTEST_ARGS) \
|
$(PYTEST) $(PYTEST_ARGS) \
|
||||||
@ -175,34 +261,27 @@ inspircd-anope:
|
|||||||
--controller=irctest.controllers.inspircd \
|
--controller=irctest.controllers.inspircd \
|
||||||
--services-controller=irctest.controllers.anope_services \
|
--services-controller=irctest.controllers.anope_services \
|
||||||
-m 'services' \
|
-m 'services' \
|
||||||
-k '$(INSPIRCD_SELECTORS)'
|
-k '$(INSPIRCD_SELECTORS) $(ANOPE_SELECTORS)'
|
||||||
|
|
||||||
ircu2:
|
ircu2:
|
||||||
$(PYTEST) $(PYTEST_ARGS) \
|
$(PYTEST) $(PYTEST_ARGS) \
|
||||||
--controller=irctest.controllers.ircu2 \
|
--controller=irctest.controllers.ircu2 \
|
||||||
-m 'not services and not IRCv3' \
|
-m 'not services and not IRCv3' \
|
||||||
-n 4 \
|
-n 10 \
|
||||||
-k '$(IRCU2_SELECTORS)'
|
-k '$(IRCU2_SELECTORS)'
|
||||||
|
|
||||||
nefarious:
|
|
||||||
$(PYTEST) $(PYTEST_ARGS) \
|
|
||||||
--controller=irctest.controllers.nefarious \
|
|
||||||
-m 'not services' \
|
|
||||||
-n 4 \
|
|
||||||
-k '$(NEFARIOUS_SELECTORS)'
|
|
||||||
|
|
||||||
snircd:
|
snircd:
|
||||||
$(PYTEST) $(PYTEST_ARGS) \
|
$(PYTEST) $(PYTEST_ARGS) \
|
||||||
--controller=irctest.controllers.snircd \
|
--controller=irctest.controllers.snircd \
|
||||||
-m 'not services and not IRCv3' \
|
-m 'not services and not IRCv3' \
|
||||||
-n 4 \
|
-n 10 \
|
||||||
-k '$(SNIRCD_SELECTORS)'
|
-k '$(SNIRCD_SELECTORS)'
|
||||||
|
|
||||||
irc2:
|
irc2:
|
||||||
$(PYTEST) $(PYTEST_ARGS) \
|
$(PYTEST) $(PYTEST_ARGS) \
|
||||||
--controller=irctest.controllers.irc2 \
|
--controller=irctest.controllers.irc2 \
|
||||||
-m 'not services and not IRCv3' \
|
-m 'not services and not IRCv3' \
|
||||||
-n 4 \
|
-n 10 \
|
||||||
-k '$(IRC2_SELECTORS)'
|
-k '$(IRC2_SELECTORS)'
|
||||||
|
|
||||||
limnoria:
|
limnoria:
|
||||||
@ -225,7 +304,7 @@ ngircd:
|
|||||||
$(PYTEST) $(PYTEST_ARGS) \
|
$(PYTEST) $(PYTEST_ARGS) \
|
||||||
--controller irctest.controllers.ngircd \
|
--controller irctest.controllers.ngircd \
|
||||||
-m 'not services' \
|
-m 'not services' \
|
||||||
-n 4 \
|
-n 10 \
|
||||||
-k "$(NGIRCD_SELECTORS)"
|
-k "$(NGIRCD_SELECTORS)"
|
||||||
|
|
||||||
ngircd-anope:
|
ngircd-anope:
|
||||||
@ -273,11 +352,4 @@ unrealircd-anope:
|
|||||||
--controller=irctest.controllers.unrealircd \
|
--controller=irctest.controllers.unrealircd \
|
||||||
--services-controller=irctest.controllers.anope_services \
|
--services-controller=irctest.controllers.anope_services \
|
||||||
-m 'services' \
|
-m 'services' \
|
||||||
-k '$(UNREALIRCD_SELECTORS)'
|
-k '$(UNREALIRCD_SELECTORS) $(ANOPE_SELECTORS)'
|
||||||
|
|
||||||
unrealircd-dlk:
|
|
||||||
pifpaf run mysql -- $(PYTEST) $(PYTEST_ARGS) \
|
|
||||||
--controller=irctest.controllers.unrealircd \
|
|
||||||
--services-controller=irctest.controllers.dlk_services \
|
|
||||||
-m 'services' \
|
|
||||||
-k '$(UNREALIRCD_SELECTORS)'
|
|
||||||
|
@ -18,11 +18,11 @@ have no side effect.
|
|||||||
Install irctest and dependencies:
|
Install irctest and dependencies:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt install faketime # Optional, but greatly speeds up irctest/server_tests/list.py
|
|
||||||
cd ~
|
cd ~
|
||||||
git clone https://github.com/ProgVal/irctest.git
|
git clone https://github.com/ProgVal/irctest.git
|
||||||
cd irctest
|
cd irctest
|
||||||
pip3 install --user -r requirements.txt
|
pip3 install --user -r requirements.txt
|
||||||
|
python3 setup.py install --user
|
||||||
```
|
```
|
||||||
|
|
||||||
Add `~/.local/bin/` (and/or `~/go/bin/` for Ergo)
|
Add `~/.local/bin/` (and/or `~/go/bin/` for Ergo)
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDT0URxi7/l7ZGe
|
|
||||||
tkPv9Yh8h2s9BpbAR4Wq8sakgqETWg/nE/JQM5dPxroVbtZWWQXuJEFsgBKbASLa
|
|
||||||
/eg5cyJv4Uu5WIZpG1LxdPEEIOSMWjzoAGwoLxbTRGrS7qNXsknB9RwDuq8lPQiK
|
|
||||||
kiAahg1Cn1vRrQ4cRrG+AkQWpRHJEDoLjCSo8IcAsKAZlw/eGtAcmeNvkr5AujEw
|
|
||||||
XjIwx2FoDyKaNGRH5Z7gLWvCKBNxQuJuMTzh8guLqdGbE4hH3rqyICbW5DGPaOZL
|
|
||||||
LErWuJ7kEhLZG2HDW5JaXOr0QfFYAA8pl9/qCuFMdoxRUKRcYBoxoMmz6dlsmipN
|
|
||||||
7vIj+TT6TemwcAT25pwMJIVS4WC4+BZilNH2eWKD9hZA8Kq7FDPu+1rxOJaLbE/b
|
|
||||||
vpK8jZeRdqFzE1eBCgPkw8D8V0r7J18d+DsmgOe2kRycaia/t9M4rhqe0FXjX1X1
|
|
||||||
lzQ52grxgc28Ejd1fGQXIJmdTh4BqKqTzxup0izS7dgFP1Ezm6Z4O+wklpL5uQF2
|
|
||||||
Ex4X6QEj76iCxH+J/01/cvbxMe3iuGXECbO/y1FIrg7FKzZSrQo4aP63lS7Y7aq0
|
|
||||||
t2t6kOS83ebhnpgHClgFs8/C3ayzYBBtbK63PYthwO8Rt6WamCIZFF5tA3XoI4Ak
|
|
||||||
fZcWD18loZai+QznVzbLNINf++rTwwIDAQABAoICAQCs1tT3piZHU2yAyo9zLbJa
|
|
||||||
kxGxcT/v1CzBSmtG8ATJyrKxRzhxszdj9HABbzjcqrXJFbKA+5yy+OFdOtSUlFtk
|
|
||||||
Wb21lwPOnmo29sp4KPL1h+itEzMuMwZ4DBry1aFZvPSsnPpoHJwwUbY3hHdHzVzi
|
|
||||||
oTCGTqT188Wzmxu+MqHppCEJLSj45ZPzvyxU1UwwW0a4H+ZTM7WlEYlzw1lHLlpQ
|
|
||||||
VBFTLS8q77aNjOKiQptiz0X+zpS0dhJvu3l7BhwtMRS8prmqnffG4r0QWCsVPP8C
|
|
||||||
cbEJkWtbwswQikN6Xpi1yw6UTQZ8brZa810aOSh07EJTfrU35rjxAndEspbJPd+4
|
|
||||||
Zz6fKNnRA7A4fFap2hF4TSP/UF12b4ulQ8FfcMMTFszWko5M6fWFuCeWfNbyXML5
|
|
||||||
fmn+NmSOboj7LkDqbpxtyaIVXXb2Y3F6A2fNllm/mxaGrRoEGNaH3o0qBgWRzJJB
|
|
||||||
TDSvIQtJddzL+iMaqxz4ufXAREJernZmPa3vlkVGLINNQUC9JLrB5eFjLzPiZN2U
|
|
||||||
8RgQ9YX5tjoJ+DtPWuMFDiCS1ZE20/UBOEYTeqIVuKdK3AjJDMFSjg8fRvsWRqZe
|
|
||||||
zsHv6tCtIFZFxYRxtrRGTUPQF+1QD6zBjYxZZk1B4n3uYBGVQFM/LnNHUxRnJBx1
|
|
||||||
PunD4ICOY97xd2hcPmGiCQKCAQEA8NCXYaHzhv6fg95H/iMuJVcOCKrJ5rVr4poG
|
|
||||||
SD0KZtS7SLzUYat8WcuoSubh5Eb2hHtrsnLrSOTnwQUO61f4gCRm2sEqHYsOAd7+
|
|
||||||
mNe1jfil0UBVqqL9GBcGYJkc5+DHgUlJQaxMV+4YLt8fD0KfZEnHaDAYX3kUdz+p
|
|
||||||
be//YAKv+JmxWcUdBF60AUWPjbCJT/1pfJeY8nEBFiYtlYKKN24+4OiRdJ2yRGzt
|
|
||||||
ZtNHaWy5EFF70yVgPX5MGQ7Z2JpejzK+lt+9nG4h1uJ4M2X4YrGVrRCn1W8jwqm/
|
|
||||||
bXest3E6wkkLoWDm9EaeYj00DUgMOviPyP4ckyxilG+Fny4JbwKCAQEA4SyUV03X
|
|
||||||
KEoL5sOD69sLu3TpnIQz73u9an9W/f2f7rsGwmCcR9RdYGV29ltwfBvOm0FnPQio
|
|
||||||
GliN+3PTWAL6bb8VYo2IR53VKpVHKSQUlzDOD9PGObXw1CT/+0zoMP7FBA4dTJDf
|
|
||||||
xQ63AQNpSCGdwbxZygPWzLV5O1WxMeXhnQRL1EBvMyJ52od0+HbajDXg5mNiBKNQ
|
|
||||||
AtVhB9pEu47Blu/KBqWjfh/GeBLPZB7MHmGNBYbBGGskKRLG4sIbwShs9cx8UM0/
|
|
||||||
9dxXkX2d8hjnSD/0ZBh54HHUaEvKAKfpz1L8AC0ll7biCAy0CZK23AoZu/KT8zJ+
|
|
||||||
qvz3AuJcW3lo7QKCAQEAzfGltNJawPUamBzNttKBUV+s2c6tkkdO92C/xKGnNp/x
|
|
||||||
dtg+TTTpyKV5zGy9fIsPoecnCFptS06vwAvCYZQ/Kd93stcFXHSiSwlY9H9tfffK
|
|
||||||
XzTEzoRLLIHsa0omRUufcrqpEqf2NjChr9wS5OsWAx9xkHGpNmUHEqB4FlPsM0C5
|
|
||||||
G0LdQCdplGYlTP0fMo5qL+VJhErle1kXE8kcrMMRzyvSTGe4lWGTph79vDUt2kQn
|
|
||||||
1IPLAJzzPEO5cqiXtzz1Z0N/aOn5b0FkYTAWmeY30LeMiJA46Df+/ihLVKPHKq6E
|
|
||||||
EMmFT8LeYMPQCbXLwRv/kaMm3D4tU9PejpD9Vk95swKCAQAtULBlxXeIVyaAAVba
|
|
||||||
L1H0Hroo0n41MtzSwt+568G05JSep5yr4/QKw0CmoY5Im7v/iLEDGmviKXIhaZTd
|
|
||||||
wHOvhGYEWGFVsFDG6hXRFL7EEoFVtBPPZ2sY9n1BkJ+lxI/XmhORZhJycNypaotU
|
|
||||||
hddets4HFrCyr86+/ybS2OWHmOa9x13Zl5WYQexrWFfxIaKqGtQOBOPEPjbxwp5U
|
|
||||||
dI1HF+i7X7hAWJqzbW2pQ31mm9EqjIztoho73diCp/e37q/G46kdBcFadEZ3NCWG
|
|
||||||
JDbfVmeTgU19usq5Vo9HhINMQvIOAwfuuVJRtmTBDHKaY7n8FfxqU/4j4RbA0Ncv
|
|
||||||
XYadAoIBAQC7yh4/UZCGhklUhhk/667OfchWvWGriCSaYGmQPdMzxjnIjAvvIUe9
|
|
||||||
riOTBSZXYYLmZHsmY/jK7KMGB3AsLTypSa9+ddAWqWn2dvOYyxNiAaSJK/RZfA9A
|
|
||||||
ocVfvvkhOfNAYIF+A+fyJ2pznsDkBf9tPkhN7kovl+mr/e25qZb1d09377770Pi7
|
|
||||||
thzEi+JLrRgYVLrCrPi2j4l7/Va/UaAPz+Dtu2GCT9vXgnhZtpb8R1kTViZFryTv
|
|
||||||
k+qbNYJzVm61Vit9mVAGe+WuzhlclJnN6LIZGG3zYHIulRAJrH1XDauHZfHzCKgi
|
|
||||||
FnMesy4thDMH/MhUfRtbylZTq45gtvCA
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIFazCCA1OgAwIBAgIUYHD08+9S32VTD9IEsr2Oe1dH3VEwDQYJKoZIhvcNAQEL
|
|
||||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
|
||||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA0MDQxODE2NTZaFw0yMzA0
|
|
||||||
MDQxODE2NTZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
|
||||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
|
|
||||||
AQUAA4ICDwAwggIKAoICAQDT0URxi7/l7ZGetkPv9Yh8h2s9BpbAR4Wq8sakgqET
|
|
||||||
Wg/nE/JQM5dPxroVbtZWWQXuJEFsgBKbASLa/eg5cyJv4Uu5WIZpG1LxdPEEIOSM
|
|
||||||
WjzoAGwoLxbTRGrS7qNXsknB9RwDuq8lPQiKkiAahg1Cn1vRrQ4cRrG+AkQWpRHJ
|
|
||||||
EDoLjCSo8IcAsKAZlw/eGtAcmeNvkr5AujEwXjIwx2FoDyKaNGRH5Z7gLWvCKBNx
|
|
||||||
QuJuMTzh8guLqdGbE4hH3rqyICbW5DGPaOZLLErWuJ7kEhLZG2HDW5JaXOr0QfFY
|
|
||||||
AA8pl9/qCuFMdoxRUKRcYBoxoMmz6dlsmipN7vIj+TT6TemwcAT25pwMJIVS4WC4
|
|
||||||
+BZilNH2eWKD9hZA8Kq7FDPu+1rxOJaLbE/bvpK8jZeRdqFzE1eBCgPkw8D8V0r7
|
|
||||||
J18d+DsmgOe2kRycaia/t9M4rhqe0FXjX1X1lzQ52grxgc28Ejd1fGQXIJmdTh4B
|
|
||||||
qKqTzxup0izS7dgFP1Ezm6Z4O+wklpL5uQF2Ex4X6QEj76iCxH+J/01/cvbxMe3i
|
|
||||||
uGXECbO/y1FIrg7FKzZSrQo4aP63lS7Y7aq0t2t6kOS83ebhnpgHClgFs8/C3ayz
|
|
||||||
YBBtbK63PYthwO8Rt6WamCIZFF5tA3XoI4AkfZcWD18loZai+QznVzbLNINf++rT
|
|
||||||
wwIDAQABo1MwUTAdBgNVHQ4EFgQU+9eHi2eqy0f3fDS0GjqkijGDDocwHwYDVR0j
|
|
||||||
BBgwFoAU+9eHi2eqy0f3fDS0GjqkijGDDocwDwYDVR0TAQH/BAUwAwEB/zANBgkq
|
|
||||||
hkiG9w0BAQsFAAOCAgEAAJXO3qUc/PW75pI2dt1cKv20VqozkfEf7P0eeVisCDxn
|
|
||||||
1p3QhVgI2lEe9kzdHp7t42g5xLkUhQEVmBcKm9xbl+k2D1X0+T8px1x6ZiWfbhXL
|
|
||||||
ptc/qCIXjPCgVN3s+Kasii3hHkZxKGZz/ySmBmfDJZjQZtbZzQWpvvX6SD4s7sjo
|
|
||||||
gWbZW3qvQ0bFTGdD1IjKYGaxK6aSrNkAIutiAX4RczJ1QSwb9Z2EIen+ABAvOZS9
|
|
||||||
xv3LiiidWcuOT7WzXEa4QvOslCEkAF+jj6mGYB7NWtly0kj4AEPvI4IoYTi9dohS
|
|
||||||
CA0zd1DTfjRwpAnT5P4sj4mpjLyRBumeeVGpCZhUxfKpFjIB2AnlgxrU+LPq5c9R
|
|
||||||
ZZ9Q5oeLxjRPjpqBeWwgnbjXstQCL9g0U7SsEemsv+zmvG5COhAmG5Wce/65ILlg
|
|
||||||
450H4bcn1ul0xvxz9hat6tqEZry3HcNE/CGDT+tXuhHpqOXkY1/c78C0QbWjWodR
|
|
||||||
tCvlXW00a+7TlEhNr4XBNdqtIQfYS9K9yiVVNfZLPEsN/SA3BGXmrr+du1/E4Ria
|
|
||||||
CkVpmBdJsVu5eMaUj1arsCqI4fwHzljtojJe/pCzZBVkOaSWQEQ+LL4iVnMas68m
|
|
||||||
qyshtNf4KNiM55OQmyTiFHMTIxCtdEcHaR3mUxR7GrIhc/bxyxUUBtMAuUX0Kjs=
|
|
||||||
-----END CERTIFICATE-----
|
|
@ -2,12 +2,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import os
|
import os
|
||||||
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 IO, Any, Callable, Dict, List, Optional, Set, Tuple, Type
|
from typing import IO, Any, Callable, Dict, List, Optional, Set, Tuple, Type
|
||||||
|
|
||||||
@ -89,7 +87,7 @@ class DirectoryBasedController(_BaseController):
|
|||||||
"""Helper for controllers whose software configuration is based on an
|
"""Helper for controllers whose software configuration is based on an
|
||||||
arbitrary directory."""
|
arbitrary directory."""
|
||||||
|
|
||||||
directory: Optional[Path]
|
directory: Optional[str]
|
||||||
|
|
||||||
def __init__(self, test_config: TestCaseControllerConfig):
|
def __init__(self, test_config: TestCaseControllerConfig):
|
||||||
super().__init__(test_config)
|
super().__init__(test_config)
|
||||||
@ -112,21 +110,22 @@ class DirectoryBasedController(_BaseController):
|
|||||||
"""Open a file in the configuration directory."""
|
"""Open a file in the configuration directory."""
|
||||||
assert self.directory
|
assert self.directory
|
||||||
if os.sep in name:
|
if os.sep in name:
|
||||||
dir_ = self.directory / os.path.dirname(name)
|
dir_ = os.path.join(self.directory, os.path.dirname(name))
|
||||||
dir_.mkdir(parents=True, exist_ok=True)
|
if not os.path.isdir(dir_):
|
||||||
assert dir_.is_dir()
|
os.makedirs(dir_)
|
||||||
return (self.directory / name).open(mode)
|
assert os.path.isdir(dir_)
|
||||||
|
return open(os.path.join(self.directory, name), mode)
|
||||||
|
|
||||||
def create_config(self) -> None:
|
def create_config(self) -> None:
|
||||||
if not self.directory:
|
if not self.directory:
|
||||||
self.directory = Path(tempfile.mkdtemp())
|
self.directory = tempfile.mkdtemp()
|
||||||
|
|
||||||
def gen_ssl(self) -> None:
|
def gen_ssl(self) -> None:
|
||||||
assert self.directory
|
assert self.directory
|
||||||
self.csr_path = self.directory / "ssl.csr"
|
self.csr_path = os.path.join(self.directory, "ssl.csr")
|
||||||
self.key_path = self.directory / "ssl.key"
|
self.key_path = os.path.join(self.directory, "ssl.key")
|
||||||
self.pem_path = self.directory / "ssl.pem"
|
self.pem_path = os.path.join(self.directory, "ssl.pem")
|
||||||
self.dh_path = self.directory / "dh.pem"
|
self.dh_path = os.path.join(self.directory, "dh.pem")
|
||||||
subprocess.check_output(
|
subprocess.check_output(
|
||||||
[
|
[
|
||||||
self.openssl_bin,
|
self.openssl_bin,
|
||||||
@ -157,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):
|
||||||
@ -198,10 +189,6 @@ class BaseServerController(_BaseController):
|
|||||||
"""Character used for the 'mute' extban"""
|
"""Character used for the 'mute' extban"""
|
||||||
nickserv = "NickServ"
|
nickserv = "NickServ"
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.faketime_enabled = False
|
|
||||||
|
|
||||||
def get_hostname_and_port(self) -> Tuple[str, int]:
|
def get_hostname_and_port(self) -> Tuple[str, int]:
|
||||||
return find_hostname_and_port()
|
return find_hostname_and_port()
|
||||||
|
|
||||||
@ -215,7 +202,6 @@ class BaseServerController(_BaseController):
|
|||||||
run_services: bool,
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]],
|
valid_metadata_keys: Optional[Set[str]],
|
||||||
invalid_metadata_keys: Optional[Set[str]],
|
invalid_metadata_keys: Optional[Set[str]],
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -231,7 +217,6 @@ class BaseServerController(_BaseController):
|
|||||||
raise NotImplementedByController("account registration")
|
raise NotImplementedByController("account registration")
|
||||||
|
|
||||||
def wait_for_port(self) -> None:
|
def wait_for_port(self) -> None:
|
||||||
started_at = time.time()
|
|
||||||
while not self.port_open:
|
while not self.port_open:
|
||||||
self.check_is_alive()
|
self.check_is_alive()
|
||||||
time.sleep(self._port_wait_interval)
|
time.sleep(self._port_wait_interval)
|
||||||
@ -254,16 +239,11 @@ class BaseServerController(_BaseController):
|
|||||||
# ircu2 cuts the connection without a message if registration
|
# ircu2 cuts the connection without a message if registration
|
||||||
# is not complete.
|
# is not complete.
|
||||||
pass
|
pass
|
||||||
except socket.timeout:
|
|
||||||
# irc2 just keeps it open
|
|
||||||
pass
|
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
self.port_open = True
|
self.port_open = True
|
||||||
except ConnectionRefusedError:
|
except Exception:
|
||||||
if time.time() - started_at >= 60:
|
continue
|
||||||
# waited for 60 seconds, giving up
|
|
||||||
raise
|
|
||||||
|
|
||||||
def wait_for_services(self) -> None:
|
def wait_for_services(self) -> None:
|
||||||
assert self.services_controller
|
assert self.services_controller
|
||||||
@ -310,11 +290,10 @@ class BaseServicesController(_BaseController):
|
|||||||
c.sendLine("PONG :" + msg.params[0])
|
c.sendLine("PONG :" + msg.params[0])
|
||||||
c.getMessages()
|
c.getMessages()
|
||||||
|
|
||||||
timeout = time.time() + 3
|
timeout = time.time() + 5
|
||||||
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
|
||||||
@ -340,12 +319,11 @@ class BaseServicesController(_BaseController):
|
|||||||
c.disconnect()
|
c.disconnect()
|
||||||
self.services_up = True
|
self.services_up = True
|
||||||
|
|
||||||
def getNickServResponse(self, client: Any, timeout: int = 0) -> List[Message]:
|
def getNickServResponse(self, client: Any) -> 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] = []
|
||||||
start_time = time.time()
|
while not msgs:
|
||||||
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
|
||||||
|
131
irctest/cases.py
131
irctest/cases.py
@ -69,30 +69,6 @@ TController = TypeVar("TController", bound=basecontrollers._BaseController)
|
|||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
def retry(f: TCallable) -> TCallable:
|
|
||||||
"""Retry the function if it raises ConnectionClosed; as a workaround for flaky
|
|
||||||
connection, such as::
|
|
||||||
|
|
||||||
1: connects to server.
|
|
||||||
1 -> S: NICK foo
|
|
||||||
1 -> S: USER username * * :Realname
|
|
||||||
S -> 1: :My.Little.Server NOTICE * :*** Found your hostname (cached)
|
|
||||||
S -> 1: :My.Little.Server NOTICE * :*** Checking Ident
|
|
||||||
S -> 1: :My.Little.Server NOTICE * :*** No Ident response
|
|
||||||
S -> 1: ERROR :Closing Link: cpu-pool.com (Use a different port)
|
|
||||||
"""
|
|
||||||
|
|
||||||
@functools.wraps(f)
|
|
||||||
def newf(*args, **kwargs): # type: ignore
|
|
||||||
try:
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
except ConnectionClosed:
|
|
||||||
time.sleep(1)
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
return newf # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
class ChannelJoinException(Exception):
|
class ChannelJoinException(Exception):
|
||||||
def __init__(self, code: str, params: List[str]):
|
def __init__(self, code: str, params: List[str]):
|
||||||
super().__init__(f"Failed to join channel ({code}): {params}")
|
super().__init__(f"Failed to join channel ({code}): {params}")
|
||||||
@ -173,7 +149,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 +327,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 +424,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 +434,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:
|
||||||
@ -534,12 +508,6 @@ class BaseServerTestCase(
|
|||||||
server_support: Optional[Dict[str, Optional[str]]]
|
server_support: Optional[Dict[str, Optional[str]]]
|
||||||
run_services = False
|
run_services = False
|
||||||
|
|
||||||
faketime: Optional[str] = None
|
|
||||||
"""If not None and the controller supports it and libfaketime is available,
|
|
||||||
runs the server using faketime and this value set as the $FAKETIME env variable.
|
|
||||||
Tests must check ``self.controller.faketime_enabled`` is True before
|
|
||||||
relying on this."""
|
|
||||||
|
|
||||||
__new__ = object.__new__ # pytest won't collect Generic[] subclasses otherwise
|
__new__ = object.__new__ # pytest won't collect Generic[] subclasses otherwise
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
@ -554,7 +522,6 @@ class BaseServerTestCase(
|
|||||||
invalid_metadata_keys=self.invalid_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,
|
|
||||||
)
|
)
|
||||||
self.clients: Dict[TClientName, client_mock.ClientMock] = {}
|
self.clients: Dict[TClientName, client_mock.ClientMock] = {}
|
||||||
|
|
||||||
@ -687,11 +654,10 @@ class BaseServerTestCase(
|
|||||||
m = self.getRegistrationMessage(client)
|
m = self.getRegistrationMessage(client)
|
||||||
self.assertIn(m.command, ["900", "903"], str(m))
|
self.assertIn(m.command, ["900", "903"], str(m))
|
||||||
|
|
||||||
@retry
|
|
||||||
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 +672,7 @@ class BaseServerTestCase(
|
|||||||
client = self.addClient(name, show_io=show_io)
|
client = self.addClient(name, show_io=show_io)
|
||||||
if capabilities:
|
if capabilities:
|
||||||
self.sendLine(client, "CAP LS 302")
|
self.sendLine(client, "CAP LS 302")
|
||||||
self.getCapLs(client)
|
m = self.getRegistrationMessage(client)
|
||||||
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 ()):
|
||||||
@ -736,12 +702,6 @@ 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]
|
|
||||||
item.split(":", 1)
|
|
||||||
for item in (self.server_support.get("TARGMAX") or "").split(",")
|
|
||||||
if item
|
|
||||||
)
|
|
||||||
|
|
||||||
return welcome
|
return welcome
|
||||||
|
|
||||||
def joinClient(self, client: TClientName, channel: str) -> None:
|
def joinClient(self, client: TClientName, channel: str) -> None:
|
||||||
@ -772,55 +732,50 @@ class BaseServerTestCase(
|
|||||||
raise ChannelJoinException(msg.command, msg.params)
|
raise ChannelJoinException(msg.command, msg.params)
|
||||||
|
|
||||||
|
|
||||||
_TSelf = TypeVar("_TSelf", bound="_IrcTestCase")
|
_TSelf = TypeVar("_TSelf", bound="OptionalityHelper")
|
||||||
_TReturn = TypeVar("_TReturn")
|
_TReturn = TypeVar("_TReturn")
|
||||||
|
|
||||||
|
|
||||||
def skipUnlessHasMechanism(
|
class OptionalityHelper(Generic[TController]):
|
||||||
mech: str,
|
controller: TController
|
||||||
) -> Callable[[Callable[..., _TReturn]], Callable[..., _TReturn]]:
|
|
||||||
# Just a function returning a function that takes functions and
|
def checkSaslSupport(self) -> None:
|
||||||
# returns functions, nothing to see here.
|
if self.controller.supported_sasl_mechanisms:
|
||||||
# If Python didn't have such an awful syntax for callables, it would be:
|
return
|
||||||
# str -> ((TSelf -> TReturn) -> (TSelf -> TReturn))
|
raise runner.NotImplementedByController("SASL")
|
||||||
def decorator(f: Callable[..., _TReturn]) -> Callable[..., _TReturn]:
|
|
||||||
|
def checkMechanismSupport(self, mechanism: str) -> None:
|
||||||
|
if mechanism in self.controller.supported_sasl_mechanisms:
|
||||||
|
return
|
||||||
|
raise runner.OptionalSaslMechanismNotSupported(mechanism)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def skipUnlessHasMechanism(
|
||||||
|
mech: str,
|
||||||
|
) -> Callable[[Callable[..., _TReturn]], Callable[..., _TReturn]]:
|
||||||
|
# Just a function returning a function that takes functions and
|
||||||
|
# returns functions, nothing to see here.
|
||||||
|
# If Python didn't have such an awful syntax for callables, it would be:
|
||||||
|
# str -> ((TSelf -> TReturn) -> (TSelf -> TReturn))
|
||||||
|
def decorator(f: Callable[..., _TReturn]) -> Callable[..., _TReturn]:
|
||||||
|
@functools.wraps(f)
|
||||||
|
def newf(self: _TSelf, *args: Any, **kwargs: Any) -> _TReturn:
|
||||||
|
self.checkMechanismSupport(mech)
|
||||||
|
return f(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return newf
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def skipUnlessHasSasl(f: Callable[..., _TReturn]) -> Callable[..., _TReturn]:
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def newf(self: _TSelf, *args: Any, **kwargs: Any) -> _TReturn:
|
def newf(self: _TSelf, *args: Any, **kwargs: Any) -> _TReturn:
|
||||||
if mech not in self.controller.supported_sasl_mechanisms:
|
self.checkSaslSupport()
|
||||||
raise runner.OptionalSaslMechanismNotSupported(mech)
|
|
||||||
return f(self, *args, **kwargs)
|
return f(self, *args, **kwargs)
|
||||||
|
|
||||||
return newf
|
return newf
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def xfailIf(
|
|
||||||
condition: Callable[..., bool], reason: str
|
|
||||||
) -> Callable[[Callable[..., _TReturn]], Callable[..., _TReturn]]:
|
|
||||||
# Works about the same as skipUnlessHasMechanism
|
|
||||||
def decorator(f: Callable[..., _TReturn]) -> Callable[..., _TReturn]:
|
|
||||||
@functools.wraps(f)
|
|
||||||
def newf(self: _TSelf, *args: Any, **kwargs: Any) -> _TReturn:
|
|
||||||
if condition(self):
|
|
||||||
try:
|
|
||||||
return f(self, *args, **kwargs)
|
|
||||||
except Exception:
|
|
||||||
pytest.xfail(reason)
|
|
||||||
assert False # make mypy happy
|
|
||||||
else:
|
|
||||||
return f(self, *args, **kwargs)
|
|
||||||
|
|
||||||
return newf
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def xfailIfSoftware(
|
|
||||||
names: List[str], reason: str
|
|
||||||
) -> Callable[[Callable[..., _TReturn]], Callable[..., _TReturn]]:
|
|
||||||
return xfailIf(lambda testcase: testcase.controller.software_name in names, reason)
|
|
||||||
|
|
||||||
|
|
||||||
def mark_services(cls: TClass) -> TClass:
|
def mark_services(cls: TClass) -> TClass:
|
||||||
cls.run_services = True
|
cls.run_services = True
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
"""Format of ``CAP LS`` sent by IRCv3 clients."""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.irc_utils.message_parser import Message
|
from irctest.irc_utils.message_parser import Message
|
||||||
|
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
"""SASL authentication from clients, for all known mechanisms.
|
|
||||||
|
|
||||||
For now, only `SASLv3.1 <https://ircv3.net/specs/extensions/sasl-3.1>`_
|
|
||||||
is tested, not `SASLv3.2 <https://ircv3.net/specs/extensions/sasl-3.2>`_."""
|
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -39,8 +34,8 @@ class IdentityHash:
|
|||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
|
|
||||||
class SaslTestCase(cases.BaseClientTestCase):
|
class SaslTestCase(cases.BaseClientTestCase, cases.OptionalityHelper):
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPlain(self):
|
def testPlain(self):
|
||||||
"""Test PLAIN authentication with correct username/password."""
|
"""Test PLAIN authentication with correct username/password."""
|
||||||
auth = authentication.Authentication(
|
auth = authentication.Authentication(
|
||||||
@ -60,8 +55,7 @@ class SaslTestCase(cases.BaseClientTestCase):
|
|||||||
m = self.negotiateCapabilities(["sasl"], False)
|
m = self.negotiateCapabilities(["sasl"], False)
|
||||||
self.assertEqual(m, Message({}, None, "CAP", ["END"]))
|
self.assertEqual(m, Message({}, None, "CAP", ["END"]))
|
||||||
|
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
@cases.xfailIfSoftware(["Sopel"], "Sopel requests SASL PLAIN even if not available")
|
|
||||||
def testPlainNotAvailable(self):
|
def testPlainNotAvailable(self):
|
||||||
"""`sasl=EXTERNAL` is advertized, whereas the client is configured
|
"""`sasl=EXTERNAL` is advertized, whereas the client is configured
|
||||||
to use PLAIN.
|
to use PLAIN.
|
||||||
@ -91,7 +85,7 @@ class SaslTestCase(cases.BaseClientTestCase):
|
|||||||
self.assertMessageMatch(m, command="CAP")
|
self.assertMessageMatch(m, command="CAP")
|
||||||
|
|
||||||
@pytest.mark.parametrize("pattern", ["barbaz", "éèà"])
|
@pytest.mark.parametrize("pattern", ["barbaz", "éèà"])
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPlainLarge(self, pattern):
|
def testPlainLarge(self, pattern):
|
||||||
"""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.
|
||||||
@ -120,7 +114,7 @@ class SaslTestCase(cases.BaseClientTestCase):
|
|||||||
m = self.negotiateCapabilities(["sasl"], False)
|
m = self.negotiateCapabilities(["sasl"], False)
|
||||||
self.assertEqual(m, Message({}, None, "CAP", ["END"]))
|
self.assertEqual(m, Message({}, None, "CAP", ["END"]))
|
||||||
|
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
@pytest.mark.parametrize("pattern", ["quux", "éè"])
|
@pytest.mark.parametrize("pattern", ["quux", "éè"])
|
||||||
def testPlainLargeMultiple(self, pattern):
|
def testPlainLargeMultiple(self, pattern):
|
||||||
"""Test the client splits large AUTHENTICATE messages whose payload
|
"""Test the client splits large AUTHENTICATE messages whose payload
|
||||||
@ -151,7 +145,7 @@ class SaslTestCase(cases.BaseClientTestCase):
|
|||||||
self.assertEqual(m, Message({}, None, "CAP", ["END"]))
|
self.assertEqual(m, Message({}, None, "CAP", ["END"]))
|
||||||
|
|
||||||
@pytest.mark.skipif(ecdsa is None, reason="python3-ecdsa is not available")
|
@pytest.mark.skipif(ecdsa is None, reason="python3-ecdsa is not available")
|
||||||
@cases.skipUnlessHasMechanism("ECDSA-NIST256P-CHALLENGE")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("ECDSA-NIST256P-CHALLENGE")
|
||||||
def testEcdsa(self):
|
def testEcdsa(self):
|
||||||
"""Test ECDSA authentication."""
|
"""Test ECDSA authentication."""
|
||||||
auth = authentication.Authentication(
|
auth = authentication.Authentication(
|
||||||
@ -185,7 +179,7 @@ class SaslTestCase(cases.BaseClientTestCase):
|
|||||||
m = self.negotiateCapabilities(["sasl"], False)
|
m = self.negotiateCapabilities(["sasl"], False)
|
||||||
self.assertEqual(m, Message({}, None, "CAP", ["END"]))
|
self.assertEqual(m, Message({}, None, "CAP", ["END"]))
|
||||||
|
|
||||||
@cases.skipUnlessHasMechanism("SCRAM-SHA-256")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("SCRAM-SHA-256")
|
||||||
def testScram(self):
|
def testScram(self):
|
||||||
"""Test SCRAM-SHA-256 authentication."""
|
"""Test SCRAM-SHA-256 authentication."""
|
||||||
auth = authentication.Authentication(
|
auth = authentication.Authentication(
|
||||||
@ -227,7 +221,7 @@ class SaslTestCase(cases.BaseClientTestCase):
|
|||||||
self.assertEqual(m.command, "AUTHENTICATE", m)
|
self.assertEqual(m.command, "AUTHENTICATE", m)
|
||||||
self.assertEqual(m.params, ["+"], m)
|
self.assertEqual(m.params, ["+"], m)
|
||||||
|
|
||||||
@cases.skipUnlessHasMechanism("SCRAM-SHA-256")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("SCRAM-SHA-256")
|
||||||
def testScramBadPassword(self):
|
def testScramBadPassword(self):
|
||||||
"""Test SCRAM-SHA-256 authentication with a bad password."""
|
"""Test SCRAM-SHA-256 authentication with a bad password."""
|
||||||
auth = authentication.Authentication(
|
auth = authentication.Authentication(
|
||||||
@ -262,8 +256,8 @@ class SaslTestCase(cases.BaseClientTestCase):
|
|||||||
authenticator.response(msg)
|
authenticator.response(msg)
|
||||||
|
|
||||||
|
|
||||||
class Irc302SaslTestCase(cases.BaseClientTestCase):
|
class Irc302SaslTestCase(cases.BaseClientTestCase, cases.OptionalityHelper):
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPlainNotAvailable(self):
|
def testPlainNotAvailable(self):
|
||||||
"""Test the client does not try to authenticate using a mechanism the
|
"""Test the client does not try to authenticate using a mechanism the
|
||||||
server does not advertise.
|
server does not advertise.
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
"""Clients should validate certificates; either with a CA or fingerprints."""
|
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
@ -140,7 +138,7 @@ class TlsTestCase(cases.BaseClientTestCase):
|
|||||||
self.getMessage()
|
self.getMessage()
|
||||||
|
|
||||||
|
|
||||||
class StsTestCase(cases.BaseClientTestCase):
|
class StsTestCase(cases.BaseClientTestCase, cases.OptionalityHelper):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.insecure_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.insecure_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from pathlib import Path
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Type
|
from typing import Type
|
||||||
@ -73,8 +73,6 @@ module {{ name = "ns_cert" }}
|
|||||||
class AnopeController(BaseServicesController, DirectoryBasedController):
|
class AnopeController(BaseServicesController, DirectoryBasedController):
|
||||||
"""Collaborator for server controllers that rely on Anope"""
|
"""Collaborator for server controllers that rely on Anope"""
|
||||||
|
|
||||||
software_name = "Anope"
|
|
||||||
|
|
||||||
def run(self, protocol: str, server_hostname: str, server_port: int) -> None:
|
def run(self, protocol: str, server_hostname: str, server_port: int) -> None:
|
||||||
self.create_config()
|
self.create_config()
|
||||||
|
|
||||||
@ -101,11 +99,14 @@ class AnopeController(BaseServicesController, DirectoryBasedController):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
assert self.directory
|
assert self.directory
|
||||||
services_path = shutil.which("services")
|
|
||||||
assert services_path
|
|
||||||
|
|
||||||
# Config and code need to be in the same directory, *obviously*
|
# Config and code need to be in the same directory, *obviously*
|
||||||
(self.directory / "lib").symlink_to(Path(services_path).parent.parent / "lib")
|
os.symlink(
|
||||||
|
os.path.join(
|
||||||
|
os.path.dirname(shutil.which("services")), "..", "lib" # type: ignore
|
||||||
|
),
|
||||||
|
os.path.join(self.directory, "lib"),
|
||||||
|
)
|
||||||
|
|
||||||
self.proc = subprocess.Popen(
|
self.proc = subprocess.Popen(
|
||||||
[
|
[
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Type
|
from typing import Optional, Type
|
||||||
|
|
||||||
@ -55,8 +56,6 @@ saslserv {{
|
|||||||
class AthemeController(BaseServicesController, DirectoryBasedController):
|
class AthemeController(BaseServicesController, DirectoryBasedController):
|
||||||
"""Mixin for server controllers that rely on Atheme"""
|
"""Mixin for server controllers that rely on Atheme"""
|
||||||
|
|
||||||
software_name = "Atheme"
|
|
||||||
|
|
||||||
def run(self, protocol: str, server_hostname: str, server_port: int) -> None:
|
def run(self, protocol: str, server_hostname: str, server_port: int) -> None:
|
||||||
self.create_config()
|
self.create_config()
|
||||||
|
|
||||||
@ -80,11 +79,11 @@ class AthemeController(BaseServicesController, DirectoryBasedController):
|
|||||||
"atheme-services",
|
"atheme-services",
|
||||||
"-n", # don't fork
|
"-n", # don't fork
|
||||||
"-c",
|
"-c",
|
||||||
self.directory / "services.conf",
|
os.path.join(self.directory, "services.conf"),
|
||||||
"-l",
|
"-l",
|
||||||
f"/tmp/services-{server_port}.log",
|
f"/tmp/services-{server_port}.log",
|
||||||
"-p",
|
"-p",
|
||||||
self.directory / "services.pid",
|
os.path.join(self.directory, "services.pid"),
|
||||||
"-D",
|
"-D",
|
||||||
self.directory,
|
self.directory,
|
||||||
],
|
],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from pathlib import Path
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Set, Type
|
from typing import Optional, Set, Type
|
||||||
@ -80,19 +80,6 @@ oper {{
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def initialize_entropy(directory: Path) -> None:
|
|
||||||
# https://github.com/DALnet/bahamut/blob/7fc039d403f66a954225c5dc4ad1fe683aedd794/include/dh.h#L35-L38
|
|
||||||
nb_rand_bytes = 512 // 8
|
|
||||||
# https://github.com/DALnet/bahamut/blob/7fc039d403f66a954225c5dc4ad1fe683aedd794/src/dh.c#L186
|
|
||||||
entropy_file_size = nb_rand_bytes * 4
|
|
||||||
|
|
||||||
# Not actually random; but we don't care.
|
|
||||||
entropy = b"\x00" * entropy_file_size
|
|
||||||
|
|
||||||
with (directory / ".ircd.entropy").open("wb") as fd:
|
|
||||||
fd.write(entropy)
|
|
||||||
|
|
||||||
|
|
||||||
class BahamutController(BaseServerController, DirectoryBasedController):
|
class BahamutController(BaseServerController, DirectoryBasedController):
|
||||||
software_name = "Bahamut"
|
software_name = "Bahamut"
|
||||||
supported_sasl_mechanisms: Set[str] = set()
|
supported_sasl_mechanisms: Set[str] = set()
|
||||||
@ -115,7 +102,6 @@ class BahamutController(BaseServerController, DirectoryBasedController):
|
|||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
@ -134,14 +120,9 @@ class BahamutController(BaseServerController, DirectoryBasedController):
|
|||||||
|
|
||||||
assert self.directory
|
assert self.directory
|
||||||
|
|
||||||
# Bahamut reads some bytes from /dev/urandom on startup, which causes
|
|
||||||
# GitHub Actions to sometimes freeze and timeout.
|
|
||||||
# This initializes the entropy file so Bahamut does not need to do it itself.
|
|
||||||
initialize_entropy(self.directory)
|
|
||||||
|
|
||||||
# they are hardcoded... thankfully Bahamut reads them from the CWD.
|
# they are hardcoded... thankfully Bahamut reads them from the CWD.
|
||||||
shutil.copy(self.pem_path, self.directory / "ircd.crt")
|
shutil.copy(self.pem_path, os.path.join(self.directory, "ircd.crt"))
|
||||||
shutil.copy(self.key_path, self.directory / "ircd.key")
|
shutil.copy(self.key_path, os.path.join(self.directory, "ircd.key"))
|
||||||
|
|
||||||
with self.open_file("server.conf") as fd:
|
with self.open_file("server.conf") as fd:
|
||||||
fd.write(
|
fd.write(
|
||||||
@ -155,21 +136,15 @@ class BahamutController(BaseServerController, DirectoryBasedController):
|
|||||||
# pem_path=self.pem_path,
|
# pem_path=self.pem_path,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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,
|
# "strace", "-f", "-e", "file",
|
||||||
"ircd",
|
"ircd",
|
||||||
"-t", # don't fork
|
"-t", # don't fork
|
||||||
"-f",
|
"-f",
|
||||||
self.directory / "server.conf",
|
os.path.join(self.directory, "server.conf"),
|
||||||
],
|
],
|
||||||
|
# stdout=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
if run_services:
|
if run_services:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import shutil
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Set
|
from typing import Optional, Set
|
||||||
|
|
||||||
@ -43,7 +43,6 @@ class BaseHybridController(BaseServerController, DirectoryBasedController):
|
|||||||
run_services: bool,
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
@ -74,22 +73,14 @@ class BaseHybridController(BaseServerController, DirectoryBasedController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert self.directory
|
assert self.directory
|
||||||
|
|
||||||
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,
|
|
||||||
self.binary_name,
|
self.binary_name,
|
||||||
"-foreground",
|
"-foreground",
|
||||||
"-configfile",
|
"-configfile",
|
||||||
self.directory / "server.conf",
|
os.path.join(self.directory, "server.conf"),
|
||||||
"-pidfile",
|
"-pidfile",
|
||||||
self.directory / "server.pid",
|
os.path.join(self.directory, "server.pid"),
|
||||||
],
|
],
|
||||||
# stderr=subprocess.DEVNULL,
|
# stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
@ -1,245 +0,0 @@
|
|||||||
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
|
|
@ -1,7 +1,6 @@
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Any, Dict, Optional, Set, Type, Union
|
from typing import Any, Dict, Optional, Set, Type, Union
|
||||||
|
|
||||||
@ -156,7 +155,6 @@ class ErgoController(BaseServerController, DirectoryBasedController):
|
|||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
config: Optional[Any] = None,
|
config: Optional[Any] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
@ -185,32 +183,27 @@ class ErgoController(BaseServerController, DirectoryBasedController):
|
|||||||
bind_address = "127.0.0.1:%s" % (port,)
|
bind_address = "127.0.0.1:%s" % (port,)
|
||||||
listener_conf = None # plaintext
|
listener_conf = None # plaintext
|
||||||
if ssl:
|
if ssl:
|
||||||
self.key_path = self.directory / "ssl.key"
|
self.key_path = os.path.join(self.directory, "ssl.key")
|
||||||
self.pem_path = self.directory / "ssl.pem"
|
self.pem_path = os.path.join(self.directory, "ssl.pem")
|
||||||
listener_conf = {"tls": {"cert": self.pem_path, "key": self.key_path}}
|
listener_conf = {"tls": {"cert": self.pem_path, "key": self.key_path}}
|
||||||
config["server"]["listeners"][bind_address] = listener_conf # type: ignore
|
config["server"]["listeners"][bind_address] = listener_conf # type: ignore
|
||||||
|
|
||||||
config["datastore"]["path"] = str(self.directory / "ircd.db") # type: ignore
|
config["datastore"]["path"] = os.path.join( # type: ignore
|
||||||
|
self.directory, "ircd.db"
|
||||||
|
)
|
||||||
|
|
||||||
if password is not None:
|
if password is not None:
|
||||||
config["server"]["password"] = hash_password(password) # type: ignore
|
config["server"]["password"] = hash_password(password) # type: ignore
|
||||||
|
|
||||||
assert self.proc is None
|
assert self.proc is None
|
||||||
|
|
||||||
self._config_path = self.directory / "server.yml"
|
self._config_path = os.path.join(self.directory, "server.yml")
|
||||||
self._config = config
|
self._config = config
|
||||||
self._write_config()
|
self._write_config()
|
||||||
subprocess.call(["ergo", "initdb", "--conf", self._config_path, "--quiet"])
|
subprocess.call(["ergo", "initdb", "--conf", self._config_path, "--quiet"])
|
||||||
subprocess.call(["ergo", "mkcerts", "--conf", self._config_path, "--quiet"])
|
subprocess.call(["ergo", "mkcerts", "--conf", self._config_path, "--quiet"])
|
||||||
|
|
||||||
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, "ergo", "run", "--conf", self._config_path, "--quiet"]
|
["ergo", "run", "--conf", self._config_path, "--quiet"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def wait_for_services(self) -> None:
|
def wait_for_services(self) -> None:
|
||||||
|
@ -42,7 +42,6 @@ class ExternalServerController(BaseServerController):
|
|||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import functools
|
import os
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Set, Type
|
from typing import Optional, Set, Type
|
||||||
|
|
||||||
@ -60,10 +59,8 @@ TEMPLATE_CONFIG = """
|
|||||||
target="services.example.org">
|
target="services.example.org">
|
||||||
|
|
||||||
# Protocol:
|
# Protocol:
|
||||||
<module name="banexception">
|
|
||||||
<module name="botmode">
|
<module name="botmode">
|
||||||
<module name="cap">
|
<module name="cap">
|
||||||
<module name="inviteexception">
|
|
||||||
<module name="ircv3">
|
<module name="ircv3">
|
||||||
<module name="ircv3_accounttag">
|
<module name="ircv3_accounttag">
|
||||||
<module name="ircv3_batch">
|
<module name="ircv3_batch">
|
||||||
@ -77,16 +74,17 @@ TEMPLATE_CONFIG = """
|
|||||||
<module name="monitor">
|
<module name="monitor">
|
||||||
<module name="m_muteban"> # for testing mute extbans
|
<module name="m_muteban"> # for testing mute extbans
|
||||||
<module name="namesx"> # For multi-prefix
|
<module name="namesx"> # For multi-prefix
|
||||||
|
<module name="noctcp">
|
||||||
<module name="sasl">
|
<module name="sasl">
|
||||||
|
|
||||||
# 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">
|
||||||
<server name="My.Little.Server" description="test server" id="000" network="testnet">
|
<server name="My.Little.Server" description="testnet" id="000" network="testnet">
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TEMPLATE_SSL_CONFIG = """
|
TEMPLATE_SSL_CONFIG = """
|
||||||
@ -95,17 +93,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"}
|
||||||
@ -128,7 +115,6 @@ class InspircdController(BaseServerController, DirectoryBasedController):
|
|||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str] = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
@ -150,13 +136,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(
|
||||||
@ -166,24 +145,15 @@ 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
|
||||||
|
|
||||||
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,
|
|
||||||
"inspircd",
|
"inspircd",
|
||||||
"--nofork",
|
"--nofork",
|
||||||
"--config",
|
"--config",
|
||||||
self.directory / "server.conf",
|
os.path.join(self.directory, "server.conf"),
|
||||||
],
|
],
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import shutil
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Set, Type
|
from typing import Optional, Set, Type
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ from irctest.basecontrollers import (
|
|||||||
|
|
||||||
TEMPLATE_CONFIG = """
|
TEMPLATE_CONFIG = """
|
||||||
# M:<Server NAME>:<YOUR Internet IP#>:<Geographic Location>:<Port>:<SID>:
|
# M:<Server NAME>:<YOUR Internet IP#>:<Geographic Location>:<Port>:<SID>:
|
||||||
M:My.Little.Server:{hostname}:test server:{port}:0042:
|
M:My.Little.Server:{hostname}:Somewhere:{port}:0042:
|
||||||
|
|
||||||
# A:<Your Name/Location>:<Your E-Mail Addr>:<other info>::<network name>:
|
# A:<Your Name/Location>:<Your E-Mail Addr>:<other info>::<network name>:
|
||||||
A:Organization, IRC dept.:Daemon <ircd@example.irc.org>:Client Server::IRCnet:
|
A:Organization, IRC dept.:Daemon <ircd@example.irc.org>:Client Server::IRCnet:
|
||||||
@ -29,8 +29,8 @@ O:*:operpassword:operuser::::
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Irc2Controller(BaseServerController, DirectoryBasedController):
|
class Ircu2Controller(BaseServerController, DirectoryBasedController):
|
||||||
software_name = "irc2"
|
binary_name: str
|
||||||
services_protocol: str
|
services_protocol: str
|
||||||
|
|
||||||
supports_sts = False
|
supports_sts = False
|
||||||
@ -51,7 +51,6 @@ class Irc2Controller(BaseServerController, DirectoryBasedController):
|
|||||||
run_services: bool,
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
@ -67,7 +66,7 @@ class Irc2Controller(BaseServerController, DirectoryBasedController):
|
|||||||
self.create_config()
|
self.create_config()
|
||||||
password_field = password if password else ""
|
password_field = password if password else ""
|
||||||
assert self.directory
|
assert self.directory
|
||||||
pidfile = self.directory / "ircd.pid"
|
pidfile = os.path.join(self.directory, "ircd.pid")
|
||||||
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(
|
||||||
@ -77,26 +76,18 @@ class Irc2Controller(BaseServerController, DirectoryBasedController):
|
|||||||
pidfile=pidfile,
|
pidfile=pidfile,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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,
|
|
||||||
"ircd",
|
"ircd",
|
||||||
"-s", # no iauth
|
"-s", # no iauth
|
||||||
"-p",
|
"-p",
|
||||||
"on",
|
"on",
|
||||||
"-f",
|
"-f",
|
||||||
self.directory / "server.conf",
|
os.path.join(self.directory, "server.conf"),
|
||||||
],
|
],
|
||||||
# stderr=subprocess.DEVNULL,
|
# stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_irctest_controller_class() -> Type[Irc2Controller]:
|
def get_irctest_controller_class() -> Type[Ircu2Controller]:
|
||||||
return Irc2Controller
|
return Ircu2Controller
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import shutil
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Set, Type
|
from typing import Optional, Set, Type
|
||||||
|
|
||||||
@ -51,7 +51,6 @@ features {{
|
|||||||
|
|
||||||
|
|
||||||
class Ircu2Controller(BaseServerController, DirectoryBasedController):
|
class Ircu2Controller(BaseServerController, DirectoryBasedController):
|
||||||
software_name = "ircu2"
|
|
||||||
supports_sts = False
|
supports_sts = False
|
||||||
extban_mute_char = None
|
extban_mute_char = None
|
||||||
|
|
||||||
@ -70,7 +69,6 @@ class Ircu2Controller(BaseServerController, DirectoryBasedController):
|
|||||||
run_services: bool,
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
@ -86,7 +84,7 @@ class Ircu2Controller(BaseServerController, DirectoryBasedController):
|
|||||||
self.create_config()
|
self.create_config()
|
||||||
password_field = 'password = "{}";'.format(password) if password else ""
|
password_field = 'password = "{}";'.format(password) if password else ""
|
||||||
assert self.directory
|
assert self.directory
|
||||||
pidfile = self.directory / "ircd.pid"
|
pidfile = os.path.join(self.directory, "ircd.pid")
|
||||||
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(
|
||||||
@ -96,20 +94,12 @@ class Ircu2Controller(BaseServerController, DirectoryBasedController):
|
|||||||
pidfile=pidfile,
|
pidfile=pidfile,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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,
|
|
||||||
"ircd",
|
"ircd",
|
||||||
"-n", # don't detach
|
"-n", # don't detach
|
||||||
"-f",
|
"-f",
|
||||||
self.directory / "server.conf",
|
os.path.join(self.directory, "server.conf"),
|
||||||
"-x",
|
"-x",
|
||||||
"DEBUG",
|
"DEBUG",
|
||||||
],
|
],
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Type
|
from typing import Optional, Type
|
||||||
|
|
||||||
@ -84,7 +85,9 @@ class LimnoriaController(BaseClientController, DirectoryBasedController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert self.directory
|
assert self.directory
|
||||||
self.proc = subprocess.Popen(["supybot", self.directory / "bot.conf"])
|
self.proc = subprocess.Popen(
|
||||||
|
["supybot", os.path.join(self.directory, "bot.conf")]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_irctest_controller_class() -> Type[LimnoriaController]:
|
def get_irctest_controller_class() -> Type[LimnoriaController]:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import shutil
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Set, Type
|
from typing import Optional, Set, Type
|
||||||
|
|
||||||
@ -92,7 +92,6 @@ class MammonController(BaseServerController, DirectoryBasedController):
|
|||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if password is not None:
|
if password is not None:
|
||||||
raise NotImplementedByController("PASS command")
|
raise NotImplementedByController("PASS command")
|
||||||
@ -114,20 +113,12 @@ class MammonController(BaseServerController, DirectoryBasedController):
|
|||||||
# with self.open_file('server.yml', 'r') as fd:
|
# with self.open_file('server.yml', 'r') as fd:
|
||||||
# print(fd.read())
|
# print(fd.read())
|
||||||
assert self.directory
|
assert self.directory
|
||||||
|
|
||||||
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,
|
|
||||||
"mammond",
|
"mammond",
|
||||||
"--nofork", # '--debug',
|
"--nofork", # '--debug',
|
||||||
"--config",
|
"--config",
|
||||||
self.directory / "server.yml",
|
os.path.join(self.directory, "server.yml"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
from typing import Type
|
|
||||||
|
|
||||||
from .ircu2 import Ircu2Controller
|
|
||||||
|
|
||||||
|
|
||||||
class NefariousController(Ircu2Controller):
|
|
||||||
software_name = "Nefarious"
|
|
||||||
|
|
||||||
|
|
||||||
def get_irctest_controller_class() -> Type[NefariousController]:
|
|
||||||
return NefariousController
|
|
@ -1,4 +1,4 @@
|
|||||||
import shutil
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Set, Type
|
from typing import Optional, Set, Type
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ from irctest.irc_utils.junkdrawer import find_hostname_and_port
|
|||||||
TEMPLATE_CONFIG = """
|
TEMPLATE_CONFIG = """
|
||||||
[Global]
|
[Global]
|
||||||
Name = My.Little.Server
|
Name = My.Little.Server
|
||||||
Info = test server
|
Info = ExampleNET Server
|
||||||
Bind = {hostname}
|
Bind = {hostname}
|
||||||
Ports = {port}
|
Ports = {port}
|
||||||
AdminInfo1 = Bob Smith
|
AdminInfo1 = Bob Smith
|
||||||
@ -56,7 +56,6 @@ class NgircdController(BaseServerController, DirectoryBasedController):
|
|||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
@ -82,7 +81,6 @@ class NgircdController(BaseServerController, DirectoryBasedController):
|
|||||||
fd.write("\n")
|
fd.write("\n")
|
||||||
|
|
||||||
assert self.directory
|
assert self.directory
|
||||||
|
|
||||||
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(
|
||||||
@ -93,23 +91,15 @@ class NgircdController(BaseServerController, DirectoryBasedController):
|
|||||||
password_field=password_field,
|
password_field=password_field,
|
||||||
key_path=self.key_path,
|
key_path=self.key_path,
|
||||||
pem_path=self.pem_path,
|
pem_path=self.pem_path,
|
||||||
empty_file=self.directory / "empty.txt",
|
empty_file=os.path.join(self.directory, "empty.txt"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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,
|
|
||||||
"ngircd",
|
"ngircd",
|
||||||
"--nodaemon",
|
"--nodaemon",
|
||||||
"--config",
|
"--config",
|
||||||
self.directory / "server.conf",
|
os.path.join(self.directory, "server.conf"),
|
||||||
],
|
],
|
||||||
# stdout=subprocess.DEVNULL,
|
# stdout=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
@ -74,7 +74,7 @@ operator {{
|
|||||||
|
|
||||||
|
|
||||||
class Plexus4Controller(BaseHybridController):
|
class Plexus4Controller(BaseHybridController):
|
||||||
software_name = "Plexus4"
|
software_name = "Hybrid"
|
||||||
binary_name = "ircd"
|
binary_name = "ircd"
|
||||||
services_protocol = "plexus"
|
services_protocol = "plexus"
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import shutil
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Set, Type
|
from typing import Optional, Set, Type
|
||||||
|
|
||||||
@ -69,7 +69,6 @@ class SnircdController(BaseServerController, DirectoryBasedController):
|
|||||||
run_services: bool,
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
@ -85,7 +84,7 @@ class SnircdController(BaseServerController, DirectoryBasedController):
|
|||||||
self.create_config()
|
self.create_config()
|
||||||
password_field = 'password = "{}";'.format(password) if password else ""
|
password_field = 'password = "{}";'.format(password) if password else ""
|
||||||
assert self.directory
|
assert self.directory
|
||||||
pidfile = self.directory / "ircd.pid"
|
pidfile = os.path.join(self.directory, "ircd.pid")
|
||||||
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(
|
||||||
@ -95,20 +94,12 @@ class SnircdController(BaseServerController, DirectoryBasedController):
|
|||||||
pidfile=pidfile,
|
pidfile=pidfile,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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,
|
|
||||||
"ircd",
|
"ircd",
|
||||||
"-n", # don't detach
|
"-n", # don't detach
|
||||||
"-f",
|
"-f",
|
||||||
self.directory / "server.conf",
|
os.path.join(self.directory, "server.conf"),
|
||||||
"-x",
|
"-x",
|
||||||
"DEBUG",
|
"DEBUG",
|
||||||
],
|
],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from pathlib import Path
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Optional, TextIO, Type, cast
|
from typing import Optional, TextIO, Type, cast
|
||||||
@ -38,14 +38,14 @@ class SopelController(BaseClientController):
|
|||||||
super().kill()
|
super().kill()
|
||||||
if self.filename:
|
if self.filename:
|
||||||
try:
|
try:
|
||||||
(Path("~/.sopel/").expanduser() / self.filename).unlink()
|
os.unlink(os.path.join(os.path.expanduser("~/.sopel/"), self.filename))
|
||||||
except OSError: # File does not exist
|
except OSError: # File does not exist
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def open_file(self, filename: str, mode: str = "a") -> TextIO:
|
def open_file(self, filename: str, mode: str = "a") -> TextIO:
|
||||||
dir_path = Path("~/.sopel/").expanduser()
|
dir_path = os.path.expanduser("~/.sopel/")
|
||||||
dir_path.mkdir(parents=True, exist_ok=True)
|
os.makedirs(dir_path, exist_ok=True)
|
||||||
return cast(TextIO, (dir_path / filename).open(mode))
|
return cast(TextIO, open(os.path.join(dir_path, filename), mode))
|
||||||
|
|
||||||
def create_config(self) -> None:
|
def create_config(self) -> None:
|
||||||
with self.open_file(self.filename):
|
with self.open_file(self.filename):
|
||||||
@ -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]:
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import contextlib
|
|
||||||
import fcntl
|
|
||||||
import functools
|
import functools
|
||||||
from pathlib import Path
|
import os
|
||||||
|
import pathlib
|
||||||
import shutil
|
import shutil
|
||||||
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Callable, ContextManager, Iterator, Optional, Set, Type
|
from typing import Optional, Set, Type
|
||||||
|
|
||||||
from irctest.basecontrollers import (
|
from irctest.basecontrollers import (
|
||||||
BaseServerController,
|
BaseServerController,
|
||||||
@ -22,7 +22,7 @@ include "help/help.conf";
|
|||||||
|
|
||||||
me {{
|
me {{
|
||||||
name "My.Little.Server";
|
name "My.Little.Server";
|
||||||
info "test server";
|
info "ExampleNET Server";
|
||||||
sid "001";
|
sid "001";
|
||||||
}}
|
}}
|
||||||
admin {{
|
admin {{
|
||||||
@ -100,9 +100,6 @@ set {{
|
|||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
modes-on-join "+H 100:1d"; // Enables CHATHISTORY
|
modes-on-join "+H 100:1d"; // Enables CHATHISTORY
|
||||||
|
|
||||||
{set_extras}
|
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
tld {{
|
tld {{
|
||||||
@ -125,35 +122,6 @@ oper "operuser" {{
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _filelock(path: Path) -> Callable[[], ContextManager]:
|
|
||||||
"""Alternative to :cls:`multiprocessing.Lock` that works with pytest-xdist"""
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def f() -> Iterator[None]:
|
|
||||||
with open(path, "a") as fd:
|
|
||||||
fcntl.flock(fd, fcntl.LOCK_EX)
|
|
||||||
yield
|
|
||||||
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
_UNREALIRCD_BIN = shutil.which("unrealircd")
|
|
||||||
if _UNREALIRCD_BIN:
|
|
||||||
_UNREALIRCD_PREFIX = Path(_UNREALIRCD_BIN).parent.parent
|
|
||||||
|
|
||||||
# Try to keep that lock file specific to this Unrealircd instance
|
|
||||||
_LOCK_PATH = _UNREALIRCD_PREFIX / "irctest-unrealircd-startstop.lock"
|
|
||||||
else:
|
|
||||||
# unrealircd not found; we are probably going to crash later anyway...
|
|
||||||
_LOCK_PATH = Path("/tmp/irctest-unrealircd-startstop.lock")
|
|
||||||
|
|
||||||
_STARTSTOP_LOCK = _filelock(_LOCK_PATH)
|
|
||||||
"""
|
|
||||||
Unreal cleans its tmp/ directory after each run, which prevents
|
|
||||||
multiple processes from starting/stopping at the same time.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def installed_version() -> int:
|
def installed_version() -> int:
|
||||||
output = subprocess.check_output(["unrealircd", "-v"], universal_newlines=True)
|
output = subprocess.check_output(["unrealircd", "-v"], universal_newlines=True)
|
||||||
@ -171,7 +139,6 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
supports_sts = False
|
supports_sts = False
|
||||||
|
|
||||||
extban_mute_char = "quiet" if installed_version() >= 6 else "q"
|
extban_mute_char = "quiet" if installed_version() >= 6 else "q"
|
||||||
software_version = installed_version()
|
|
||||||
|
|
||||||
def create_config(self) -> None:
|
def create_config(self) -> None:
|
||||||
super().create_config()
|
super().create_config()
|
||||||
@ -189,7 +156,6 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
faketime: Optional[str],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if valid_metadata_keys or invalid_metadata_keys:
|
if valid_metadata_keys or invalid_metadata_keys:
|
||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
@ -199,6 +165,18 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
self.port = port
|
self.port = port
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
self.create_config()
|
self.create_config()
|
||||||
|
(unused_hostname, unused_port) = find_hostname_and_port()
|
||||||
|
(services_hostname, services_port) = find_hostname_and_port()
|
||||||
|
|
||||||
|
password_field = 'password "{}";'.format(password) if password else ""
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
if installed_version() >= 6:
|
if installed_version() >= 6:
|
||||||
extras = textwrap.dedent(
|
extras = textwrap.dedent(
|
||||||
@ -207,78 +185,60 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
loadmodule "cloak_md5";
|
loadmodule "cloak_md5";
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
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_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 ""
|
assert self.directory
|
||||||
|
with self.open_file("unrealircd.conf") as fd:
|
||||||
with _STARTSTOP_LOCK():
|
fd.write(
|
||||||
(services_hostname, services_port) = find_hostname_and_port()
|
TEMPLATE_CONFIG.format(
|
||||||
(unused_hostname, unused_port) = find_hostname_and_port()
|
hostname=hostname,
|
||||||
|
port=port,
|
||||||
self.gen_ssl()
|
services_hostname=services_hostname,
|
||||||
if ssl:
|
services_port=services_port,
|
||||||
(tls_hostname, tls_port) = (hostname, port)
|
tls_hostname=tls_hostname,
|
||||||
(hostname, port) = (unused_hostname, unused_port)
|
tls_port=tls_port,
|
||||||
else:
|
password_field=password_field,
|
||||||
# Unreal refuses to start without TLS enabled
|
key_path=self.key_path,
|
||||||
(tls_hostname, tls_port) = (unused_hostname, unused_port)
|
pem_path=self.pem_path,
|
||||||
|
empty_file=os.path.join(self.directory, "empty.txt"),
|
||||||
assert self.directory
|
extras=extras,
|
||||||
|
|
||||||
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(
|
|
||||||
[
|
|
||||||
*faketime_cmd,
|
|
||||||
"unrealircd",
|
|
||||||
"-t",
|
|
||||||
"-F", # BOOT_NOFORK
|
|
||||||
"-f",
|
|
||||||
self.directory / "unrealircd.conf",
|
|
||||||
],
|
|
||||||
# stdout=subprocess.DEVNULL,
|
|
||||||
)
|
)
|
||||||
self.wait_for_port()
|
|
||||||
|
proot_cmd = []
|
||||||
|
self.using_proot = False
|
||||||
|
if shutil.which("proot"):
|
||||||
|
unrealircd_path = shutil.which("unrealircd")
|
||||||
|
if unrealircd_path:
|
||||||
|
unrealircd_prefix = pathlib.Path(unrealircd_path).parents[1]
|
||||||
|
tmpdir = os.path.join(self.directory, "tmp")
|
||||||
|
os.mkdir(tmpdir)
|
||||||
|
# Unreal cleans its tmp/ directory after each run, which prevents
|
||||||
|
# multiple processes from running at the same time.
|
||||||
|
# Using PRoot, we can isolate them, with a tmp/ directory for each
|
||||||
|
# process, so they don't interfere with each other, allowing use of
|
||||||
|
# the -n option (of pytest-xdist) to speed-up tests
|
||||||
|
proot_cmd = ["proot", "-b", f"{tmpdir}:{unrealircd_prefix}/tmp"]
|
||||||
|
self.using_proot = True
|
||||||
|
|
||||||
|
self.proc = subprocess.Popen(
|
||||||
|
[
|
||||||
|
*proot_cmd,
|
||||||
|
"unrealircd",
|
||||||
|
"-t",
|
||||||
|
"-F", # BOOT_NOFORK
|
||||||
|
"-f",
|
||||||
|
os.path.join(self.directory, "unrealircd.conf"),
|
||||||
|
],
|
||||||
|
# stdout=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
if run_services:
|
if run_services:
|
||||||
|
self.wait_for_port()
|
||||||
self.services_controller = self.services_controller_class(
|
self.services_controller = self.services_controller_class(
|
||||||
self.test_config, self
|
self.test_config, self
|
||||||
)
|
)
|
||||||
@ -288,13 +248,17 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
server_port=services_port,
|
server_port=services_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
def kill_proc(self) -> None:
|
def kill(self) -> None:
|
||||||
assert self.proc
|
if self.using_proot:
|
||||||
|
# Kill grandchild process, instead of killing proot, which takes more
|
||||||
with _STARTSTOP_LOCK():
|
# time (and does not seem to always work)
|
||||||
self.proc.kill()
|
assert self.proc is not None
|
||||||
self.proc.wait(5) # wait for it to actually die
|
output = subprocess.check_output(
|
||||||
self.proc = None
|
["ps", "-opid", "--no-headers", "--ppid", str(self.proc.pid)]
|
||||||
|
)
|
||||||
|
(grandchild_pid,) = [int(line) for line in output.decode().split()]
|
||||||
|
os.kill(grandchild_pid, signal.SIGKILL)
|
||||||
|
super().kill()
|
||||||
|
|
||||||
|
|
||||||
def get_irctest_controller_class() -> Type[UnrealircdController]:
|
def get_irctest_controller_class() -> Type[UnrealircdController]:
|
||||||
|
@ -1,449 +0,0 @@
|
|||||||
import base64
|
|
||||||
import dataclasses
|
|
||||||
import gzip
|
|
||||||
import hashlib
|
|
||||||
import importlib
|
|
||||||
from pathlib import Path
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from typing import (
|
|
||||||
IO,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
Iterable,
|
|
||||||
Iterator,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Tuple,
|
|
||||||
TypeVar,
|
|
||||||
)
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
from defusedxml.ElementTree import parse as parse_xml
|
|
||||||
import docutils.core
|
|
||||||
|
|
||||||
NETLIFY_CHAR_BLACKLIST = frozenset('":<>|*?\r\n#')
|
|
||||||
"""Characters not allowed in output filenames"""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class CaseResult:
|
|
||||||
module_name: str
|
|
||||||
class_name: str
|
|
||||||
test_name: str
|
|
||||||
job: str
|
|
||||||
success: bool
|
|
||||||
skipped: bool
|
|
||||||
system_out: Optional[str]
|
|
||||||
details: Optional[str] = None
|
|
||||||
type: Optional[str] = None
|
|
||||||
message: Optional[str] = None
|
|
||||||
|
|
||||||
def output_filename(self) -> str:
|
|
||||||
test_name = self.test_name
|
|
||||||
if len(test_name) > 50 or set(test_name) & NETLIFY_CHAR_BLACKLIST:
|
|
||||||
# File name too long or otherwise invalid. This should be good enough:
|
|
||||||
m = re.match(r"(?P<function_name>\w+?)\[(?P<params>.+)\]", test_name)
|
|
||||||
assert m, "File name is too long but has no parameter."
|
|
||||||
test_name = f'{m.group("function_name")}[{md5sum(m.group("params"))}]'
|
|
||||||
return f"{self.job}_{self.module_name}.{self.class_name}.{test_name}.txt"
|
|
||||||
|
|
||||||
|
|
||||||
TK = TypeVar("TK")
|
|
||||||
TV = TypeVar("TV")
|
|
||||||
|
|
||||||
|
|
||||||
def md5sum(text: str) -> str:
|
|
||||||
return base64.urlsafe_b64encode(hashlib.md5(text.encode()).digest()).decode()
|
|
||||||
|
|
||||||
|
|
||||||
def group_by(list_: Iterable[TV], key: Callable[[TV], TK]) -> Dict[TK, List[TV]]:
|
|
||||||
groups: Dict[TK, List[TV]] = {}
|
|
||||||
for value in list_:
|
|
||||||
groups.setdefault(key(value), []).append(value)
|
|
||||||
|
|
||||||
return groups
|
|
||||||
|
|
||||||
|
|
||||||
def iter_job_results(job_file_name: Path, job: ET.ElementTree) -> Iterator[CaseResult]:
|
|
||||||
(suite,) = job.getroot()
|
|
||||||
for case in suite:
|
|
||||||
if "name" not in case.attrib:
|
|
||||||
continue
|
|
||||||
|
|
||||||
success = True
|
|
||||||
skipped = False
|
|
||||||
details = None
|
|
||||||
system_out = None
|
|
||||||
extra: Dict[str, str] = {}
|
|
||||||
for child in case:
|
|
||||||
if child.tag == "skipped":
|
|
||||||
success = True
|
|
||||||
skipped = True
|
|
||||||
details = None
|
|
||||||
extra = child.attrib
|
|
||||||
elif child.tag in ("failure", "error"):
|
|
||||||
success = False
|
|
||||||
skipped = False
|
|
||||||
details = child.text
|
|
||||||
extra = child.attrib
|
|
||||||
elif child.tag == "system-out":
|
|
||||||
assert (
|
|
||||||
system_out is None
|
|
||||||
# for some reason, skipped tests have two system-out;
|
|
||||||
# and the second one contains test teardown
|
|
||||||
or child.text.startswith(system_out.rstrip())
|
|
||||||
), ("Duplicate system-out tag", repr(system_out), repr(child.text))
|
|
||||||
system_out = child.text
|
|
||||||
else:
|
|
||||||
assert False, child
|
|
||||||
|
|
||||||
(module_name, class_name) = case.attrib["classname"].rsplit(".", 1)
|
|
||||||
m = re.match(
|
|
||||||
r"(.*/)?pytest[ -]results[ _](?P<name>.*)"
|
|
||||||
r"[ _][(]?(stable|release|devel|devel_release)[)]?/pytest.xml(.gz)?",
|
|
||||||
str(job_file_name),
|
|
||||||
)
|
|
||||||
assert m, job_file_name
|
|
||||||
yield CaseResult(
|
|
||||||
module_name=module_name,
|
|
||||||
class_name=class_name,
|
|
||||||
test_name=case.attrib["name"],
|
|
||||||
job=m.group("name"),
|
|
||||||
success=success,
|
|
||||||
skipped=skipped,
|
|
||||||
details=details,
|
|
||||||
system_out=system_out,
|
|
||||||
**extra,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def rst_to_element(s: str) -> ET.Element:
|
|
||||||
html = docutils.core.publish_parts(s, writer_name="xhtml")["html_body"]
|
|
||||||
htmltree = ET.fromstring(html)
|
|
||||||
return htmltree
|
|
||||||
|
|
||||||
|
|
||||||
def append_docstring(element: ET.Element, obj: object) -> None:
|
|
||||||
if obj.__doc__ is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
element.append(rst_to_element(obj.__doc__))
|
|
||||||
|
|
||||||
|
|
||||||
def build_job_html(job: str, results: List[CaseResult]) -> ET.Element:
|
|
||||||
jobs = sorted({result.job for result in results})
|
|
||||||
root = ET.Element("html")
|
|
||||||
head = ET.SubElement(root, "head")
|
|
||||||
ET.SubElement(head, "title").text = job
|
|
||||||
ET.SubElement(head, "link", rel="stylesheet", type="text/css", href="./style.css")
|
|
||||||
|
|
||||||
body = ET.SubElement(root, "body")
|
|
||||||
|
|
||||||
ET.SubElement(body, "h1").text = job
|
|
||||||
|
|
||||||
table = build_test_table(jobs, results)
|
|
||||||
table.set("class", "job-results test-matrix")
|
|
||||||
body.append(table)
|
|
||||||
|
|
||||||
return root
|
|
||||||
|
|
||||||
|
|
||||||
def build_module_html(
|
|
||||||
jobs: List[str], results: List[CaseResult], module_name: str
|
|
||||||
) -> ET.Element:
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
|
|
||||||
root = ET.Element("html")
|
|
||||||
head = ET.SubElement(root, "head")
|
|
||||||
ET.SubElement(head, "title").text = module_name
|
|
||||||
ET.SubElement(head, "link", rel="stylesheet", type="text/css", href="./style.css")
|
|
||||||
|
|
||||||
body = ET.SubElement(root, "body")
|
|
||||||
|
|
||||||
ET.SubElement(body, "h1").text = module_name
|
|
||||||
|
|
||||||
append_docstring(body, module)
|
|
||||||
|
|
||||||
table = build_test_table(jobs, results)
|
|
||||||
table.set("class", "module-results test-matrix")
|
|
||||||
body.append(table)
|
|
||||||
|
|
||||||
return root
|
|
||||||
|
|
||||||
|
|
||||||
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, lambda r: (r.module_name, r.class_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
table = ET.Element("table")
|
|
||||||
|
|
||||||
job_row = ET.Element("tr")
|
|
||||||
ET.SubElement(job_row, "th") # column of case name
|
|
||||||
for job in jobs:
|
|
||||||
cell = ET.SubElement(job_row, "th")
|
|
||||||
ET.SubElement(ET.SubElement(cell, "div"), "span").text = job
|
|
||||||
cell.set("class", "job-name")
|
|
||||||
|
|
||||||
for (module_name, class_name), class_results in sorted(
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Header row: class name
|
|
||||||
header_row = ET.SubElement(table, "tr")
|
|
||||||
th = ET.SubElement(header_row, "th", colspan=str(len(jobs) + 1))
|
|
||||||
row_anchor = f"{qualified_class_name}"
|
|
||||||
section_header = ET.SubElement(
|
|
||||||
ET.SubElement(th, "h2"),
|
|
||||||
"a",
|
|
||||||
href=f"#{row_anchor}",
|
|
||||||
id=row_anchor,
|
|
||||||
)
|
|
||||||
section_header.text = qualified_class_name
|
|
||||||
append_docstring(th, getattr(module, class_name))
|
|
||||||
|
|
||||||
# Header row: one column for each implementation
|
|
||||||
table.append(job_row)
|
|
||||||
|
|
||||||
# One row for each test:
|
|
||||||
results_by_test = group_by(class_results, key=lambda r: r.test_name)
|
|
||||||
for test_name, test_results in sorted(results_by_test.items()):
|
|
||||||
row_anchor = f"{qualified_class_name}.{test_name}"
|
|
||||||
if len(row_anchor) >= 50:
|
|
||||||
# Too long; give up on generating readable URL
|
|
||||||
# TODO: only hash test parameter
|
|
||||||
row_anchor = md5sum(row_anchor)
|
|
||||||
|
|
||||||
row = ET.SubElement(table, "tr", id=row_anchor)
|
|
||||||
|
|
||||||
cell = ET.SubElement(row, "th")
|
|
||||||
cell.set("class", "test-name")
|
|
||||||
cell_link = ET.SubElement(cell, "a", href=f"#{row_anchor}")
|
|
||||||
cell_link.text = test_name
|
|
||||||
|
|
||||||
results_by_job = group_by(test_results, key=lambda r: r.job)
|
|
||||||
for job_name in jobs:
|
|
||||||
cell = ET.SubElement(row, "td")
|
|
||||||
try:
|
|
||||||
(result,) = results_by_job[job_name]
|
|
||||||
except KeyError:
|
|
||||||
cell.set("class", "deselected")
|
|
||||||
cell.text = "d"
|
|
||||||
continue
|
|
||||||
|
|
||||||
text: Optional[str]
|
|
||||||
|
|
||||||
if result.skipped:
|
|
||||||
cell.set("class", "skipped")
|
|
||||||
if result.type == "pytest.skip":
|
|
||||||
text = "s"
|
|
||||||
elif result.type == "pytest.xfail":
|
|
||||||
text = "X"
|
|
||||||
cell.set("class", "expected-failure")
|
|
||||||
else:
|
|
||||||
text = result.type
|
|
||||||
elif result.success:
|
|
||||||
cell.set("class", "success")
|
|
||||||
if result.type:
|
|
||||||
# dead code?
|
|
||||||
text = result.type
|
|
||||||
else:
|
|
||||||
text = "."
|
|
||||||
else:
|
|
||||||
cell.set("class", "failure")
|
|
||||||
if result.type:
|
|
||||||
# dead code?
|
|
||||||
text = result.type
|
|
||||||
else:
|
|
||||||
text = "f"
|
|
||||||
|
|
||||||
if result.system_out:
|
|
||||||
# There is a log file; link to it.
|
|
||||||
a = ET.SubElement(cell, "a", href=f"./{result.output_filename()}")
|
|
||||||
a.text = text or "?"
|
|
||||||
else:
|
|
||||||
cell.text = text or "?"
|
|
||||||
if result.message:
|
|
||||||
cell.set("title", result.message)
|
|
||||||
|
|
||||||
return table
|
|
||||||
|
|
||||||
|
|
||||||
def write_html_pages(
|
|
||||||
output_dir: Path, results: List[CaseResult]
|
|
||||||
) -> List[Tuple[str, str, str]]:
|
|
||||||
"""Returns the list of (module_name, file_name)."""
|
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
results_by_module = group_by(results, lambda r: r.module_name)
|
|
||||||
|
|
||||||
# used as columns
|
|
||||||
jobs = list(sorted({r.job for r in results}))
|
|
||||||
|
|
||||||
job_categories = {}
|
|
||||||
for job in jobs:
|
|
||||||
is_client = any(
|
|
||||||
"client_tests" in result.module_name and result.job == job
|
|
||||||
for result in results
|
|
||||||
)
|
|
||||||
is_server = any(
|
|
||||||
"server_tests" in result.module_name and result.job == job
|
|
||||||
for result in results
|
|
||||||
)
|
|
||||||
assert is_client != is_server, (job, is_client, is_server)
|
|
||||||
if job.endswith(("-atheme", "-anope", "-dlk")):
|
|
||||||
assert is_server
|
|
||||||
job_categories[job] = "server-with-services"
|
|
||||||
elif is_server:
|
|
||||||
job_categories[job] = "server" # with or without services
|
|
||||||
else:
|
|
||||||
assert is_client
|
|
||||||
job_categories[job] = "client"
|
|
||||||
|
|
||||||
pages = []
|
|
||||||
|
|
||||||
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
|
|
||||||
module_categories = {
|
|
||||||
job_categories[result.job]
|
|
||||||
for result in results
|
|
||||||
if result.module_name == module_name and not result.skipped
|
|
||||||
}
|
|
||||||
|
|
||||||
module_jobs = [job for job in jobs if job_categories[job] in module_categories]
|
|
||||||
|
|
||||||
root = build_module_html(module_jobs, module_results, module_name)
|
|
||||||
file_name = f"{module_name}.xhtml"
|
|
||||||
write_xml_file(output_dir / file_name, root)
|
|
||||||
pages.append(("module", module_name, file_name))
|
|
||||||
|
|
||||||
for category in ("server", "client"):
|
|
||||||
for job in [job for job in job_categories if job_categories[job] == category]:
|
|
||||||
job_results = [
|
|
||||||
result
|
|
||||||
for result in results
|
|
||||||
if result.job == job or result.job.startswith(job + "-")
|
|
||||||
]
|
|
||||||
root = build_job_html(job, job_results)
|
|
||||||
file_name = f"{job}.xhtml"
|
|
||||||
write_xml_file(output_dir / file_name, root)
|
|
||||||
pages.append(("job", job, file_name))
|
|
||||||
|
|
||||||
return pages
|
|
||||||
|
|
||||||
|
|
||||||
def write_test_outputs(output_dir: Path, results: List[CaseResult]) -> None:
|
|
||||||
"""Writes stdout files of each test."""
|
|
||||||
for result in results:
|
|
||||||
if result.system_out is None:
|
|
||||||
continue
|
|
||||||
output_file = output_dir / result.output_filename()
|
|
||||||
with output_file.open("wt") as fd:
|
|
||||||
fd.write(result.system_out)
|
|
||||||
|
|
||||||
|
|
||||||
def write_html_index(output_dir: Path, pages: List[Tuple[str, str, str]]) -> None:
|
|
||||||
root = ET.Element("html")
|
|
||||||
head = ET.SubElement(root, "head")
|
|
||||||
ET.SubElement(head, "title").text = "irctest dashboard"
|
|
||||||
ET.SubElement(head, "link", rel="stylesheet", type="text/css", href="./style.css")
|
|
||||||
|
|
||||||
body = ET.SubElement(root, "body")
|
|
||||||
|
|
||||||
ET.SubElement(body, "h1").text = "irctest dashboard"
|
|
||||||
|
|
||||||
module_pages = []
|
|
||||||
job_pages = []
|
|
||||||
for page_type, title, file_name in sorted(pages):
|
|
||||||
if page_type == "module":
|
|
||||||
module_pages.append((title, file_name))
|
|
||||||
elif page_type == "job":
|
|
||||||
job_pages.append((title, file_name))
|
|
||||||
else:
|
|
||||||
assert False, page_type
|
|
||||||
|
|
||||||
ET.SubElement(body, "h2").text = "Tests by command/specification"
|
|
||||||
|
|
||||||
dl = ET.SubElement(body, "dl")
|
|
||||||
dl.set("class", "module-index")
|
|
||||||
|
|
||||||
for module_name, file_name in sorted(module_pages):
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
|
|
||||||
link = ET.SubElement(ET.SubElement(dl, "dt"), "a", href=f"./{file_name}")
|
|
||||||
link.text = module_name
|
|
||||||
append_docstring(ET.SubElement(dl, "dd"), module)
|
|
||||||
|
|
||||||
ET.SubElement(body, "h2").text = "Tests by implementation"
|
|
||||||
|
|
||||||
ul = ET.SubElement(body, "ul")
|
|
||||||
ul.set("class", "job-index")
|
|
||||||
|
|
||||||
for job, file_name in sorted(job_pages):
|
|
||||||
link = ET.SubElement(ET.SubElement(ul, "li"), "a", href=f"./{file_name}")
|
|
||||||
link.text = job
|
|
||||||
|
|
||||||
write_xml_file(output_dir / "index.xhtml", root)
|
|
||||||
|
|
||||||
|
|
||||||
def write_assets(output_dir: Path) -> None:
|
|
||||||
css_path = output_dir / "style.css"
|
|
||||||
source_css_path = Path(__file__).parent / "style.css"
|
|
||||||
with css_path.open("wt") as fd:
|
|
||||||
with source_css_path.open() as source_fd:
|
|
||||||
fd.write(source_fd.read())
|
|
||||||
|
|
||||||
|
|
||||||
def write_xml_file(filename: Path, root: ET.Element) -> None:
|
|
||||||
# Hacky: ET expects the namespace to be present in every tag we create instead;
|
|
||||||
# but it would be excessively verbose.
|
|
||||||
root.set("xmlns", "http://www.w3.org/1999/xhtml")
|
|
||||||
|
|
||||||
# Serialize
|
|
||||||
s = ET.tostring(root)
|
|
||||||
|
|
||||||
with filename.open("wb") as fd:
|
|
||||||
fd.write(s)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_xml_file(filename: Path) -> ET.ElementTree:
|
|
||||||
fd: IO
|
|
||||||
if filename.suffix == ".gz":
|
|
||||||
with gzip.open(filename, "rb") as fd: # type: ignore
|
|
||||||
return parse_xml(fd) # type: ignore
|
|
||||||
else:
|
|
||||||
with open(filename) as fd:
|
|
||||||
return parse_xml(fd) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def main(output_path: Path, filenames: List[Path]) -> int:
|
|
||||||
results = [
|
|
||||||
result
|
|
||||||
for filename in filenames
|
|
||||||
for result in iter_job_results(filename, parse_xml_file(filename))
|
|
||||||
]
|
|
||||||
|
|
||||||
pages = write_html_pages(output_path, results)
|
|
||||||
|
|
||||||
write_html_index(output_path, pages)
|
|
||||||
write_test_outputs(output_path, results)
|
|
||||||
write_assets(output_path)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
(_, output_path, *filenames) = sys.argv
|
|
||||||
exit(main(Path(output_path), list(map(Path, filenames))))
|
|
@ -1,87 +0,0 @@
|
|||||||
import dataclasses
|
|
||||||
import gzip
|
|
||||||
import io
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
|
||||||
from typing import Iterator
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class Artifact:
|
|
||||||
repo: str
|
|
||||||
run_id: int
|
|
||||||
name: str
|
|
||||||
download_url: str
|
|
||||||
|
|
||||||
@property
|
|
||||||
def public_download_url(self) -> str:
|
|
||||||
# GitHub API is not available publicly for artifacts, we need to use
|
|
||||||
# a third-party proxy to access it...
|
|
||||||
name = urllib.parse.quote(self.name)
|
|
||||||
return f"https://nightly.link/{repo}/actions/runs/{self.run_id}/{name}.zip"
|
|
||||||
|
|
||||||
|
|
||||||
def iter_run_artifacts(repo: str, run_id: int) -> Iterator[Artifact]:
|
|
||||||
request = urllib.request.Request(
|
|
||||||
f"https://api.github.com/repos/{repo}/actions/runs/{run_id}/artifacts"
|
|
||||||
"?per_page=100",
|
|
||||||
headers={"Accept": "application/vnd.github.v3+json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
response = urllib.request.urlopen(request)
|
|
||||||
|
|
||||||
for artifact in json.load(response)["artifacts"]:
|
|
||||||
if not artifact["name"].startswith(("pytest-results_", "pytest results ")):
|
|
||||||
continue
|
|
||||||
if artifact["expired"]:
|
|
||||||
continue
|
|
||||||
yield Artifact(
|
|
||||||
repo=repo,
|
|
||||||
run_id=run_id,
|
|
||||||
name=artifact["name"],
|
|
||||||
download_url=artifact["archive_download_url"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def download_artifact(output_name: Path, url: str) -> None:
|
|
||||||
if output_name.exists():
|
|
||||||
return
|
|
||||||
response = urllib.request.urlopen(url)
|
|
||||||
archive_bytes = response.read() # Can't stream it, it's a ZIP
|
|
||||||
with zipfile.ZipFile(io.BytesIO(archive_bytes)) as archive:
|
|
||||||
with archive.open("pytest.xml") as input_fd:
|
|
||||||
pytest_xml = input_fd.read()
|
|
||||||
|
|
||||||
tmp_output_path = output_name.with_suffix(".tmp")
|
|
||||||
with gzip.open(tmp_output_path, "wb") as output_fd:
|
|
||||||
output_fd.write(pytest_xml)
|
|
||||||
|
|
||||||
# Atomically write to the output path, so that we don't write partial files in case
|
|
||||||
# the download process is interrupted
|
|
||||||
tmp_output_path.rename(output_name)
|
|
||||||
|
|
||||||
|
|
||||||
def main(output_dir: Path, repo: str, run_id: int) -> int:
|
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
run_path = output_dir / str(run_id)
|
|
||||||
run_path.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
for artifact in iter_run_artifacts(repo, run_id):
|
|
||||||
artifact_path = run_path / artifact.name / "pytest.xml.gz"
|
|
||||||
artifact_path.parent.mkdir(exist_ok=True)
|
|
||||||
try:
|
|
||||||
download_artifact(artifact_path, artifact.download_url)
|
|
||||||
except Exception:
|
|
||||||
download_artifact(artifact_path, artifact.public_download_url)
|
|
||||||
print("downloaded", artifact.name)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
(_, output_path, repo, run_id) = sys.argv
|
|
||||||
exit(main(Path(output_path), repo, int(run_id)))
|
|
@ -1,67 +0,0 @@
|
|||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
body {
|
|
||||||
background-color: #121212;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
filter: invert(0.85) hue-rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.module-index {
|
|
||||||
column-width: 40em; /* Magic constant for 2 columns on average laptop/desktop */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Only 1px solid border between cells */
|
|
||||||
table.test-matrix {
|
|
||||||
border-spacing: 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
table.test-matrix td {
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make link take the whole cell */
|
|
||||||
table.test-matrix td a {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
color: black;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test matrix colors */
|
|
||||||
table.test-matrix .deselected {
|
|
||||||
background-color: grey;
|
|
||||||
}
|
|
||||||
table.test-matrix .success {
|
|
||||||
background-color: green;
|
|
||||||
}
|
|
||||||
table.test-matrix .skipped {
|
|
||||||
background-color: yellow;
|
|
||||||
}
|
|
||||||
table.test-matrix .failure {
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
table.test-matrix .expected-failure {
|
|
||||||
background-color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rotate headers, thanks to https://css-tricks.com/rotated-table-column-headers/ */
|
|
||||||
table.module-results th.job-name {
|
|
||||||
height: 140px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
table.module-results th.job-name > div {
|
|
||||||
transform:
|
|
||||||
translate(28px, 50px)
|
|
||||||
rotate(315deg);
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
table.module-results th.job-name > div > span {
|
|
||||||
border-bottom: 1px solid grey;
|
|
||||||
padding-left: 0px;
|
|
||||||
}
|
|
@ -66,7 +66,6 @@ RPL_WHOISIDLE = "317"
|
|||||||
RPL_ENDOFWHOIS = "318"
|
RPL_ENDOFWHOIS = "318"
|
||||||
RPL_WHOISCHANNELS = "319"
|
RPL_WHOISCHANNELS = "319"
|
||||||
RPL_WHOISSPECIAL = "320"
|
RPL_WHOISSPECIAL = "320"
|
||||||
RPL_LISTSTART = "321"
|
|
||||||
RPL_LIST = "322"
|
RPL_LIST = "322"
|
||||||
RPL_LISTEND = "323"
|
RPL_LISTEND = "323"
|
||||||
RPL_CHANNELMODEIS = "324"
|
RPL_CHANNELMODEIS = "324"
|
||||||
@ -142,7 +141,6 @@ ERR_USERONCHANNEL = "443"
|
|||||||
ERR_NOLOGIN = "444"
|
ERR_NOLOGIN = "444"
|
||||||
ERR_SUMMONDISABLED = "445"
|
ERR_SUMMONDISABLED = "445"
|
||||||
ERR_USERSDISABLED = "446"
|
ERR_USERSDISABLED = "446"
|
||||||
ERR_FORBIDDENCHANNEL = "448"
|
|
||||||
ERR_NOTREGISTERED = "451"
|
ERR_NOTREGISTERED = "451"
|
||||||
ERR_NEEDMOREPARAMS = "461"
|
ERR_NEEDMOREPARAMS = "461"
|
||||||
ERR_ALREADYREGISTRED = "462"
|
ERR_ALREADYREGISTRED = "462"
|
||||||
|
@ -152,23 +152,21 @@ 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)
|
||||||
|
elif isinstance(expected_key, Operator):
|
||||||
|
raise NotImplementedError(f"Unsupported operator: {expected_key}")
|
||||||
else:
|
else:
|
||||||
for key in got:
|
if expected_key not in got:
|
||||||
if match_string(key, expected_key) and match_string(
|
return False
|
||||||
got[key], expected_value
|
got_value = got.pop(expected_key)
|
||||||
):
|
if not match_string(got_value, expected_value):
|
||||||
got.pop(key)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Found no (key, value) pair matching the request
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
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):
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
"""Internal checks of assertion implementations."""
|
|
||||||
|
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -179,39 +177,6 @@ MESSAGE_SPECS: List[Tuple[Dict, List[str], List[str], List[str]]] = [
|
|||||||
"expected tags to match {'tag1': 'bar', RemainingKeys(ANYSTR): ANYOPTSTR}, got {}",
|
"expected tags to match {'tag1': 'bar', RemainingKeys(ANYSTR): ANYOPTSTR}, got {}",
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
(
|
|
||||||
# the specification:
|
|
||||||
dict(
|
|
||||||
tags={StrRe("tag[12]"): "bar", **ANYDICT},
|
|
||||||
command="PRIVMSG",
|
|
||||||
params=["#chan", "hello"],
|
|
||||||
),
|
|
||||||
# matches:
|
|
||||||
[
|
|
||||||
"@tag1=bar PRIVMSG #chan :hello",
|
|
||||||
"@tag1=bar;tag2= PRIVMSG #chan :hello",
|
|
||||||
"@tag1=bar :foo!baz@qux PRIVMSG #chan :hello",
|
|
||||||
"@tag2=bar PRIVMSG #chan :hello",
|
|
||||||
"@tag1=bar;tag2= PRIVMSG #chan :hello",
|
|
||||||
"@tag1=;tag2=bar PRIVMSG #chan :hello",
|
|
||||||
],
|
|
||||||
# and does not match:
|
|
||||||
[
|
|
||||||
"PRIVMG #chan :hello",
|
|
||||||
"@tag1=value1 PRIVMSG #chan :hello",
|
|
||||||
"PRIVMSG #chan hello2",
|
|
||||||
"PRIVMSG #chan2 hello",
|
|
||||||
":foo!baz@qux PRIVMSG #chan hello",
|
|
||||||
],
|
|
||||||
# and they each error with:
|
|
||||||
[
|
|
||||||
"expected command to be PRIVMSG, got PRIVMG",
|
|
||||||
"expected tags to match {StrRe(r'tag[12]'): 'bar', RemainingKeys(ANYSTR): ANYOPTSTR}, got {'tag1': 'value1'}",
|
|
||||||
"expected params to match ['#chan', 'hello'], got ['#chan', 'hello2']",
|
|
||||||
"expected params to match ['#chan', 'hello'], got ['#chan2', 'hello']",
|
|
||||||
"expected tags to match {StrRe(r'tag[12]'): 'bar', RemainingKeys(ANYSTR): ANYOPTSTR}, got {}",
|
|
||||||
]
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
# the specification:
|
# the specification:
|
||||||
dict(
|
dict(
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 account-tag <https://ircv3.net/specs/extensions/account-tag>`_
|
<http://ircv3.net/specs/extensions/account-tag-3.2.html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
|
||||||
|
|
||||||
@cases.mark_services
|
@cases.mark_services
|
||||||
class AccountTagTestCase(cases.BaseServerTestCase):
|
class AccountTagTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
def connectRegisteredClient(self, nick):
|
def connectRegisteredClient(self, nick):
|
||||||
self.addClient()
|
self.addClient()
|
||||||
self.sendLine(2, "CAP LS 302")
|
self.sendLine(2, "CAP LS 302")
|
||||||
@ -40,7 +40,7 @@ class AccountTagTestCase(cases.BaseServerTestCase):
|
|||||||
self.skipToWelcome(2)
|
self.skipToWelcome(2)
|
||||||
|
|
||||||
@cases.mark_capabilities("account-tag")
|
@cases.mark_capabilities("account-tag")
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPrivmsg(self):
|
def testPrivmsg(self):
|
||||||
self.connectClient("foo", capabilities=["account-tag"], skip_if_cap_nak=True)
|
self.connectClient("foo", capabilities=["account-tag"], skip_if_cap_nak=True)
|
||||||
self.getMessages(1)
|
self.getMessages(1)
|
||||||
@ -54,10 +54,7 @@ class AccountTagTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_capabilities("account-tag")
|
@cases.mark_capabilities("account-tag")
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Charybdis"], "https://github.com/solanum-ircd/solanum/issues/166"
|
|
||||||
)
|
|
||||||
def testInvite(self):
|
def testInvite(self):
|
||||||
self.connectClient("foo", capabilities=["account-tag"], skip_if_cap_nak=True)
|
self.connectClient("foo", capabilities=["account-tag"], skip_if_cap_nak=True)
|
||||||
self.getMessages(1)
|
self.getMessages(1)
|
||||||
|
@ -1,16 +1,5 @@
|
|||||||
"""
|
|
||||||
AWAY command (`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-4.1>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#away-message>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import (
|
from irctest.numerics import RPL_AWAY, RPL_NOWAWAY, RPL_UNAWAY, RPL_USERHOST
|
||||||
RPL_AWAY,
|
|
||||||
RPL_NOWAWAY,
|
|
||||||
RPL_UNAWAY,
|
|
||||||
RPL_USERHOST,
|
|
||||||
RPL_WHOISUSER,
|
|
||||||
)
|
|
||||||
from irctest.patma import StrRe
|
from irctest.patma import StrRe
|
||||||
|
|
||||||
|
|
||||||
@ -43,7 +32,7 @@ class AwayTestCase(cases.BaseServerTestCase):
|
|||||||
"""
|
"""
|
||||||
"The server acknowledges the change in away status by returning the
|
"The server acknowledges the change in away status by returning the
|
||||||
`RPL_NOWAWAY` and `RPL_UNAWAY` numerics."
|
`RPL_NOWAWAY` and `RPL_UNAWAY` numerics."
|
||||||
-- https://modern.ircdocs.horse/#away-message
|
-- https://github.com/ircdocs/modern-irc/pull/100
|
||||||
"""
|
"""
|
||||||
self.connectClient("bar")
|
self.connectClient("bar")
|
||||||
self.sendLine(1, "AWAY :I'm not here right now")
|
self.sendLine(1, "AWAY :I'm not here right now")
|
||||||
@ -59,7 +48,7 @@ class AwayTestCase(cases.BaseServerTestCase):
|
|||||||
"""
|
"""
|
||||||
"Servers SHOULD notify clients when a user they're interacting with
|
"Servers SHOULD notify clients when a user they're interacting with
|
||||||
is away when relevant"
|
is away when relevant"
|
||||||
-- https://modern.ircdocs.horse/#away-message
|
-- https://github.com/ircdocs/modern-irc/pull/100
|
||||||
|
|
||||||
"<client> <nick> :<message>"
|
"<client> <nick> :<message>"
|
||||||
-- https://modern.ircdocs.horse/#rplaway-301
|
-- https://modern.ircdocs.horse/#rplaway-301
|
||||||
@ -86,7 +75,7 @@ class AwayTestCase(cases.BaseServerTestCase):
|
|||||||
"""
|
"""
|
||||||
"Servers SHOULD notify clients when a user they're interacting with
|
"Servers SHOULD notify clients when a user they're interacting with
|
||||||
is away when relevant"
|
is away when relevant"
|
||||||
-- https://modern.ircdocs.horse/#away-message
|
-- https://github.com/ircdocs/modern-irc/pull/100
|
||||||
|
|
||||||
"<client> <nick> :<message>"
|
"<client> <nick> :<message>"
|
||||||
-- https://modern.ircdocs.horse/#rplaway-301
|
-- https://modern.ircdocs.horse/#rplaway-301
|
||||||
@ -124,7 +113,7 @@ class AwayTestCase(cases.BaseServerTestCase):
|
|||||||
"""
|
"""
|
||||||
"Servers SHOULD notify clients when a user they're interacting with
|
"Servers SHOULD notify clients when a user they're interacting with
|
||||||
is away when relevant"
|
is away when relevant"
|
||||||
-- https://modern.ircdocs.horse/#away-message
|
-- https://github.com/ircdocs/modern-irc/pull/100
|
||||||
|
|
||||||
"<client> <nick> :<message>"
|
"<client> <nick> :<message>"
|
||||||
-- https://modern.ircdocs.horse/#rplaway-301
|
-- https://modern.ircdocs.horse/#rplaway-301
|
||||||
@ -145,33 +134,3 @@ class AwayTestCase(cases.BaseServerTestCase):
|
|||||||
self.assertMessageMatch(
|
self.assertMessageMatch(
|
||||||
self.getMessage(2), command=RPL_USERHOST, params=["qux", StrRe(r"bar=-.*")]
|
self.getMessage(2), command=RPL_USERHOST, params=["qux", StrRe(r"bar=-.*")]
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
def testAwayEmptyMessage(self):
|
|
||||||
"""
|
|
||||||
"If [AWAY] is sent with a nonempty parameter (the 'away message')
|
|
||||||
then the user is set to be away. If this command is sent with no
|
|
||||||
parameters, or with the empty string as the parameter, the user is no
|
|
||||||
longer away."
|
|
||||||
-- https://modern.ircdocs.horse/#away-message
|
|
||||||
"""
|
|
||||||
self.connectClient("bar", name="bar")
|
|
||||||
self.connectClient("qux", name="qux")
|
|
||||||
|
|
||||||
self.sendLine("bar", "AWAY :I'm not here right now")
|
|
||||||
replies = self.getMessages("bar")
|
|
||||||
self.assertIn(RPL_NOWAWAY, [msg.command for msg in replies])
|
|
||||||
self.sendLine("qux", "WHOIS bar")
|
|
||||||
replies = self.getMessages("qux")
|
|
||||||
self.assertIn(RPL_WHOISUSER, [msg.command for msg in replies])
|
|
||||||
self.assertIn(RPL_AWAY, [msg.command for msg in replies])
|
|
||||||
|
|
||||||
# empty final parameter to AWAY is treated the same as no parameter,
|
|
||||||
# i.e., the client is considered to be no longer away
|
|
||||||
self.sendLine("bar", "AWAY :")
|
|
||||||
replies = self.getMessages("bar")
|
|
||||||
self.assertIn(RPL_UNAWAY, [msg.command for msg in replies])
|
|
||||||
self.sendLine("qux", "WHOIS bar")
|
|
||||||
replies = self.getMessages("qux")
|
|
||||||
self.assertIn(RPL_WHOISUSER, [msg.command for msg in replies])
|
|
||||||
self.assertNotIn(RPL_AWAY, [msg.command for msg in replies])
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 away-notify <https://ircv3.net/specs/extensions/away-notify>`_
|
<https://ircv3.net/specs/extensions/away-notify-3.1>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
|
||||||
|
|
||||||
class AwayNotifyTestCase(cases.BaseServerTestCase):
|
class AwayNotifyTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@cases.mark_capabilities("away-notify")
|
@cases.mark_capabilities("away-notify")
|
||||||
def testAwayNotify(self):
|
def testAwayNotify(self):
|
||||||
"""Basic away-notify test."""
|
"""Basic away-notify test."""
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 bot mode <https://ircv3.net/specs/extensions/bot-mode>`_
|
Draft bot mode specification, as defined in
|
||||||
|
<https://ircv3.net/specs/extensions/bot-mode>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases, runner
|
from irctest import cases, runner
|
||||||
@ -67,10 +68,6 @@ class BotModeTestCase(cases.BaseServerTestCase):
|
|||||||
message, command=RPL_WHOISBOT, params=["usernick", "botnick", ANYSTR]
|
message, command=RPL_WHOISBOT, params=["usernick", "botnick", ANYSTR]
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["InspIRCd"],
|
|
||||||
"Uses only vendor tags for now: https://github.com/inspircd/inspircd/pull/1910",
|
|
||||||
)
|
|
||||||
def testBotPrivateMessage(self):
|
def testBotPrivateMessage(self):
|
||||||
self._initBot()
|
self._initBot()
|
||||||
|
|
||||||
@ -85,13 +82,9 @@ class BotModeTestCase(cases.BaseServerTestCase):
|
|||||||
self.getMessage("user"),
|
self.getMessage("user"),
|
||||||
command="PRIVMSG",
|
command="PRIVMSG",
|
||||||
params=["usernick", "beep boop"],
|
params=["usernick", "beep boop"],
|
||||||
tags={StrRe("(draft/)?bot"): None, **ANYDICT},
|
tags={"draft/bot": None, **ANYDICT},
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["InspIRCd"],
|
|
||||||
"Uses only vendor tags for now: https://github.com/inspircd/inspircd/pull/1910",
|
|
||||||
)
|
|
||||||
def testBotChannelMessage(self):
|
def testBotChannelMessage(self):
|
||||||
self._initBot()
|
self._initBot()
|
||||||
|
|
||||||
@ -111,7 +104,7 @@ class BotModeTestCase(cases.BaseServerTestCase):
|
|||||||
self.getMessage("user"),
|
self.getMessage("user"),
|
||||||
command="PRIVMSG",
|
command="PRIVMSG",
|
||||||
params=["#chan", "beep boop"],
|
params=["#chan", "beep boop"],
|
||||||
tags={StrRe("(draft/)?bot"): None, **ANYDICT},
|
tags={"draft/bot": None, **ANYDICT},
|
||||||
)
|
)
|
||||||
|
|
||||||
def testBotWhox(self):
|
def testBotWhox(self):
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
"""
|
|
||||||
`Ergo <https://ergo.chat/>`_-specific tests of
|
|
||||||
`multiclient features
|
|
||||||
<https://github.com/ergochat/ergo/blob/master/docs/MANUAL.md#multiclient-bouncer>`_
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.irc_utils.sasl import sasl_plain_blob
|
from irctest.irc_utils.sasl import sasl_plain_blob
|
||||||
from irctest.numerics import ERR_NICKNAMEINUSE, RPL_WELCOME
|
from irctest.numerics import ERR_NICKNAMEINUSE, RPL_WELCOME
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
"""
|
"""Sends packets with various length to check the server reassembles them
|
||||||
Sends packets with various length to check the server reassembles them
|
correctly. Also checks truncation"""
|
||||||
correctly. Also checks truncation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
@ -32,16 +30,6 @@ def _sendBytePerByte(self, line):
|
|||||||
|
|
||||||
|
|
||||||
class BufferingTestCase(cases.BaseServerTestCase):
|
class BufferingTestCase(cases.BaseServerTestCase):
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Bahamut"],
|
|
||||||
"cannot pass because of issues with UTF-8 handling: "
|
|
||||||
"https://github.com/DALnet/bahamut/issues/196",
|
|
||||||
)
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["ircu2", "Nefarious", "snircd"],
|
|
||||||
"ircu2 discards the whole buffer on long lines "
|
|
||||||
"(TODO: refine how we exclude these tests)",
|
|
||||||
)
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"sender_function,colon",
|
"sender_function,colon",
|
||||||
[
|
[
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
"""
|
|
||||||
`IRCv3 Capability negotiation
|
|
||||||
<https://ircv3.net/specs/extensions/capability-negotiation>`_
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.patma import ANYSTR
|
from irctest.patma import ANYSTR
|
||||||
from irctest.runner import CapabilityNotSupported, ImplementationChoice
|
from irctest.runner import CapabilityNotSupported, ImplementationChoice
|
||||||
|
|
||||||
|
|
||||||
class CapTestCase(cases.BaseServerTestCase):
|
class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
def testNoReq(self):
|
def testNoReq(self):
|
||||||
"""Test the server handles gracefully clients which do not send
|
"""Test the server handles gracefully clients which do not send
|
||||||
@ -78,10 +73,6 @@ class CapTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["UnrealIRCd"],
|
|
||||||
"UnrealIRCd sends a trailing space on CAP NAK: https://github.com/unrealircd/unrealircd/pull/148",
|
|
||||||
)
|
|
||||||
def testNakWhole(self):
|
def testNakWhole(self):
|
||||||
"""“The capability identifier set must be accepted as a whole, or
|
"""“The capability identifier set must be accepted as a whole, or
|
||||||
rejected entirely.”
|
rejected entirely.”
|
||||||
@ -129,10 +120,6 @@ class CapTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["UnrealIRCd"],
|
|
||||||
"UnrealIRCd sends a trailing space on CAP NAK: https://github.com/unrealircd/unrealircd/pull/148",
|
|
||||||
)
|
|
||||||
def testCapRemovalByClient(self):
|
def testCapRemovalByClient(self):
|
||||||
"""Test CAP LIST and removal of caps via CAP REQ :-tagname."""
|
"""Test CAP LIST and removal of caps via CAP REQ :-tagname."""
|
||||||
cap1 = "echo-message"
|
cap1 = "echo-message"
|
||||||
@ -185,60 +172,3 @@ class CapTestCase(cases.BaseServerTestCase):
|
|||||||
enabled_caps.discard("cap-notify") # implicitly added by some impls
|
enabled_caps.discard("cap-notify") # implicitly added by some impls
|
||||||
self.assertEqual(enabled_caps, {cap1})
|
self.assertEqual(enabled_caps, {cap1})
|
||||||
self.assertNotIn("time", cap_list.tags)
|
self.assertNotIn("time", cap_list.tags)
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
|
||||||
def testIrc301CapLs(self):
|
|
||||||
"""
|
|
||||||
Current version:
|
|
||||||
|
|
||||||
"The LS subcommand is used to list the capabilities supported by the server.
|
|
||||||
The client should send an LS subcommand with no other arguments to solicit
|
|
||||||
a list of all capabilities."
|
|
||||||
|
|
||||||
"If a client has not indicated support for CAP LS 302 features,
|
|
||||||
the server MUST NOT send these new features to the client."
|
|
||||||
-- <https://ircv3.net/specs/core/capability-negotiation.html>
|
|
||||||
|
|
||||||
Before the v3.1 / v3.2 merge:
|
|
||||||
|
|
||||||
IRCv3.1: “The LS subcommand is used to list the capabilities
|
|
||||||
supported by the server. The client should send an LS subcommand with
|
|
||||||
no other arguments to solicit a list of all capabilities.”
|
|
||||||
-- <http://ircv3.net/specs/core/capability-negotiation-3.1.html#the-cap-ls-subcommand>
|
|
||||||
|
|
||||||
IRCv3.2: “Servers MUST NOT send messages described by this document if
|
|
||||||
the client only supports version 3.1.”
|
|
||||||
-- <http://ircv3.net/specs/core/capability-negotiation-3.2.html#version-in-cap-ls>
|
|
||||||
""" # noqa
|
|
||||||
self.addClient()
|
|
||||||
self.sendLine(1, "CAP LS")
|
|
||||||
m = self.getRegistrationMessage(1)
|
|
||||||
self.assertNotEqual(
|
|
||||||
m.params[2],
|
|
||||||
"*",
|
|
||||||
m,
|
|
||||||
fail_msg="Server replied with multi-line CAP LS to a "
|
|
||||||
"“CAP LS” (ie. IRCv3.1) request: {msg}",
|
|
||||||
)
|
|
||||||
self.assertFalse(
|
|
||||||
any("=" in cap for cap in m.params[2].split()),
|
|
||||||
"Server replied with a name-value capability in "
|
|
||||||
"CAP LS reply as a response to “CAP LS” (ie. IRCv3.1) "
|
|
||||||
"request: {}".format(m),
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
|
||||||
def testEmptyCapList(self):
|
|
||||||
"""“If no capabilities are active, an empty parameter must be sent.”
|
|
||||||
-- <http://ircv3.net/specs/core/capability-negotiation-3.1.html#the-cap-list-subcommand>
|
|
||||||
""" # noqa
|
|
||||||
self.addClient()
|
|
||||||
self.sendLine(1, "CAP LIST")
|
|
||||||
m = self.getRegistrationMessage(1)
|
|
||||||
self.assertMessageMatch(
|
|
||||||
m,
|
|
||||||
command="CAP",
|
|
||||||
params=["*", "LIST", ""],
|
|
||||||
fail_msg="Sending “CAP LIST” as first message got a reply "
|
|
||||||
"that is not “CAP * LIST :”: {msg}",
|
|
||||||
)
|
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
Channel casemapping
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from irctest import cases, client_mock, runner
|
from irctest import cases, client_mock, runner
|
||||||
@ -22,7 +18,7 @@ class ChannelCaseSensitivityTestCase(cases.BaseServerTestCase):
|
|||||||
self.connectClient("foo")
|
self.connectClient("foo")
|
||||||
self.connectClient("bar")
|
self.connectClient("bar")
|
||||||
if self.server_support["CASEMAPPING"] != casemapping:
|
if self.server_support["CASEMAPPING"] != casemapping:
|
||||||
raise runner.ImplementationChoice(
|
raise runner.NotImplementedByController(
|
||||||
"Casemapping {} not implemented".format(casemapping)
|
"Casemapping {} not implemented".format(casemapping)
|
||||||
)
|
)
|
||||||
self.joinClient(1, name1)
|
self.joinClient(1, name1)
|
||||||
@ -47,7 +43,7 @@ class ChannelCaseSensitivityTestCase(cases.BaseServerTestCase):
|
|||||||
self.connectClient("foo")
|
self.connectClient("foo")
|
||||||
self.connectClient("bar")
|
self.connectClient("bar")
|
||||||
if self.server_support["CASEMAPPING"] != casemapping:
|
if self.server_support["CASEMAPPING"] != casemapping:
|
||||||
raise runner.ImplementationChoice(
|
raise runner.NotImplementedByController(
|
||||||
"Casemapping {} not implemented".format(casemapping)
|
"Casemapping {} not implemented".format(casemapping)
|
||||||
)
|
)
|
||||||
self.joinClient(1, name1)
|
self.joinClient(1, name1)
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
"""
|
|
||||||
`Ergo <https://ergo.chat/>`_-specific tests of channel forwarding
|
|
||||||
|
|
||||||
TODO: Should be extended to other servers, once a specification is written.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import ERR_CHANOPRIVSNEEDED, ERR_INVALIDMODEPARAM, ERR_LINKCHANNEL
|
from irctest.numerics import ERR_CHANOPRIVSNEEDED, ERR_INVALIDMODEPARAM, ERR_LINKCHANNEL
|
||||||
|
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
"""
|
|
||||||
`Draft IRCv3 channel-rename <https://ircv3.net/specs/extensions/channel-rename>`_
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import ERR_CHANOPRIVSNEEDED
|
from irctest.numerics import ERR_CHANOPRIVSNEEDED
|
||||||
|
|
||||||
|
MODERN_CAPS = [
|
||||||
|
"server-time",
|
||||||
|
"message-tags",
|
||||||
|
"batch",
|
||||||
|
"labeled-response",
|
||||||
|
"echo-message",
|
||||||
|
"account-tag",
|
||||||
|
]
|
||||||
RENAME_CAP = "draft/channel-rename"
|
RENAME_CAP = "draft/channel-rename"
|
||||||
|
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
|
||||||
class ChannelRenameTestCase(cases.BaseServerTestCase):
|
class ChannelRenameTestCase(cases.BaseServerTestCase):
|
||||||
"""Basic tests for channel-rename."""
|
"""Basic tests for channel-rename."""
|
||||||
|
|
||||||
|
@cases.mark_specifications("Ergo")
|
||||||
def testChannelRename(self):
|
def testChannelRename(self):
|
||||||
self.connectClient(
|
self.connectClient("bar", name="bar", capabilities=MODERN_CAPS + [RENAME_CAP])
|
||||||
"bar", name="bar", capabilities=[RENAME_CAP], skip_if_cap_nak=True
|
self.connectClient("baz", name="baz", capabilities=MODERN_CAPS)
|
||||||
)
|
|
||||||
self.connectClient("baz", name="baz")
|
|
||||||
self.joinChannel("bar", "#bar")
|
self.joinChannel("bar", "#bar")
|
||||||
self.joinChannel("baz", "#bar")
|
self.joinChannel("baz", "#bar")
|
||||||
self.getMessages("bar")
|
self.getMessages("bar")
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
"""
|
|
||||||
`IRCv3 draft chathistory <https://ircv3.net/specs/extensions/chathistory>`_
|
|
||||||
"""
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from irctest import cases, runner
|
from irctest import cases
|
||||||
from irctest.irc_utils.junkdrawer import random_name
|
from irctest.irc_utils.junkdrawer import random_name
|
||||||
from irctest.patma import ANYSTR
|
from irctest.patma import ANYSTR
|
||||||
|
|
||||||
@ -43,16 +38,6 @@ def validate_chathistory_batch(msgs):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def skip_ngircd(f):
|
|
||||||
@functools.wraps(f)
|
|
||||||
def newf(self, *args, **kwargs):
|
|
||||||
if self.controller.software_name == "ngIRCd":
|
|
||||||
raise runner.OptionalExtensionNotSupported("nicks longer 9 characters")
|
|
||||||
return f(self, *args, **kwargs)
|
|
||||||
|
|
||||||
return newf
|
|
||||||
|
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.mark_services
|
@cases.mark_services
|
||||||
class ChathistoryTestCase(cases.BaseServerTestCase):
|
class ChathistoryTestCase(cases.BaseServerTestCase):
|
||||||
@ -60,7 +45,6 @@ class ChathistoryTestCase(cases.BaseServerTestCase):
|
|||||||
def config() -> cases.TestCaseControllerConfig:
|
def config() -> cases.TestCaseControllerConfig:
|
||||||
return cases.TestCaseControllerConfig(chathistory=True)
|
return cases.TestCaseControllerConfig(chathistory=True)
|
||||||
|
|
||||||
@skip_ngircd
|
|
||||||
def testInvalidTargets(self):
|
def testInvalidTargets(self):
|
||||||
bar, pw = random_name("bar"), random_name("pw")
|
bar, pw = random_name("bar"), random_name("pw")
|
||||||
self.controller.registerUser(self, bar, pw)
|
self.controller.registerUser(self, bar, pw)
|
||||||
@ -106,7 +90,6 @@ class ChathistoryTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.private_chathistory
|
@pytest.mark.private_chathistory
|
||||||
@skip_ngircd
|
|
||||||
def testMessagesToSelf(self):
|
def testMessagesToSelf(self):
|
||||||
bar, pw = random_name("bar"), random_name("pw")
|
bar, pw = random_name("bar"), random_name("pw")
|
||||||
self.controller.registerUser(self, bar, pw)
|
self.controller.registerUser(self, bar, pw)
|
||||||
@ -179,19 +162,7 @@ class ChathistoryTestCase(cases.BaseServerTestCase):
|
|||||||
self.assertEqual(len(set(msg.time for msg in echo_messages)), num_messages)
|
self.assertEqual(len(set(msg.time for msg in echo_messages)), num_messages)
|
||||||
|
|
||||||
@pytest.mark.parametrize("subcommand", SUBCOMMANDS)
|
@pytest.mark.parametrize("subcommand", SUBCOMMANDS)
|
||||||
@skip_ngircd
|
|
||||||
def testChathistory(self, subcommand):
|
def testChathistory(self, subcommand):
|
||||||
if subcommand == "BETWEEN" and self.controller.software_name == "UnrealIRCd":
|
|
||||||
pytest.xfail(
|
|
||||||
"CHATHISTORY BETWEEN does not apply bounds correct "
|
|
||||||
"https://bugs.unrealircd.org/view.php?id=5952"
|
|
||||||
)
|
|
||||||
if subcommand == "AROUND" and self.controller.software_name == "UnrealIRCd":
|
|
||||||
pytest.xfail(
|
|
||||||
"CHATHISTORY AROUND excludes 'central' messages "
|
|
||||||
"https://bugs.unrealircd.org/view.php?id=5953"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.connectClient(
|
self.connectClient(
|
||||||
"bar",
|
"bar",
|
||||||
capabilities=[
|
capabilities=[
|
||||||
@ -223,7 +194,6 @@ class ChathistoryTestCase(cases.BaseServerTestCase):
|
|||||||
self.validate_chathistory(subcommand, echo_messages, 1, chname)
|
self.validate_chathistory(subcommand, echo_messages, 1, chname)
|
||||||
|
|
||||||
@pytest.mark.parametrize("subcommand", SUBCOMMANDS)
|
@pytest.mark.parametrize("subcommand", SUBCOMMANDS)
|
||||||
@skip_ngircd
|
|
||||||
def testChathistoryEventPlayback(self, subcommand):
|
def testChathistoryEventPlayback(self, subcommand):
|
||||||
self.connectClient(
|
self.connectClient(
|
||||||
"bar",
|
"bar",
|
||||||
@ -257,7 +227,6 @@ class ChathistoryTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
@pytest.mark.parametrize("subcommand", SUBCOMMANDS)
|
@pytest.mark.parametrize("subcommand", SUBCOMMANDS)
|
||||||
@pytest.mark.private_chathistory
|
@pytest.mark.private_chathistory
|
||||||
@skip_ngircd
|
|
||||||
def testChathistoryDMs(self, subcommand):
|
def testChathistoryDMs(self, subcommand):
|
||||||
c1 = "foo" + secrets.token_hex(12)
|
c1 = "foo" + secrets.token_hex(12)
|
||||||
c2 = "bar" + secrets.token_hex(12)
|
c2 = "bar" + secrets.token_hex(12)
|
||||||
@ -580,7 +549,6 @@ class ChathistoryTestCase(cases.BaseServerTestCase):
|
|||||||
self.assertIn(echo_messages[7], result)
|
self.assertIn(echo_messages[7], result)
|
||||||
|
|
||||||
@pytest.mark.arbitrary_client_tags
|
@pytest.mark.arbitrary_client_tags
|
||||||
@skip_ngircd
|
|
||||||
def testChathistoryTagmsg(self):
|
def testChathistoryTagmsg(self):
|
||||||
c1 = "foo" + secrets.token_hex(12)
|
c1 = "foo" + secrets.token_hex(12)
|
||||||
c2 = "bar" + secrets.token_hex(12)
|
c2 = "bar" + secrets.token_hex(12)
|
||||||
@ -679,7 +647,6 @@ class ChathistoryTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
@pytest.mark.arbitrary_client_tags
|
@pytest.mark.arbitrary_client_tags
|
||||||
@pytest.mark.private_chathistory
|
@pytest.mark.private_chathistory
|
||||||
@skip_ngircd
|
|
||||||
def testChathistoryDMClientOnlyTags(self):
|
def testChathistoryDMClientOnlyTags(self):
|
||||||
# regression test for Ergo #1411
|
# regression test for Ergo #1411
|
||||||
c1 = "foo" + secrets.token_hex(12)
|
c1 = "foo" + secrets.token_hex(12)
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
"""
|
|
||||||
`Ergo <https://ergo.chat/>`_-specific tests of auditorium mode
|
|
||||||
|
|
||||||
TODO: Should be extended to other servers, once a specification is written.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
"""
|
from irctest import cases
|
||||||
Channel ban (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.3.1>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.3>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#ban-channel-mode>`__)
|
|
||||||
and ban exception (`Modern <https://modern.ircdocs.horse/#exception-channel-mode>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases, runner
|
|
||||||
from irctest.numerics import ERR_BANNEDFROMCHAN, RPL_BANLIST, RPL_ENDOFBANLIST
|
from irctest.numerics import ERR_BANNEDFROMCHAN, RPL_BANLIST, RPL_ENDOFBANLIST
|
||||||
from irctest.patma import ANYSTR, StrRe
|
from irctest.patma import ANYSTR, StrRe
|
||||||
|
|
||||||
@ -34,7 +26,7 @@ class BanModeTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
def testBanList(self):
|
def testBanList(self):
|
||||||
"""`RPL_BANLIST <https://modern.ircdocs.horse/#rplbanlist-367>`_"""
|
"""https://github.com/ircdocs/modern-irc/pull/125"""
|
||||||
self.connectClient("chanop")
|
self.connectClient("chanop")
|
||||||
self.joinChannel(1, "#chan")
|
self.joinChannel(1, "#chan")
|
||||||
self.getMessages(1)
|
self.getMessages(1)
|
||||||
@ -78,65 +70,6 @@ class BanModeTestCase(cases.BaseServerTestCase):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
def testBanException(self):
|
|
||||||
"""`Exception mode <https://modern.ircdocs.horse/#exception-channel-mode`_,
|
|
||||||
detected using `ISUPPORT EXCEPTS
|
|
||||||
<https://modern.ircdocs.horse/#excepts-parameter>`_ and checked against
|
|
||||||
`ISUPPORT CHANMODES <https://modern.ircdocs.horse/#chanmodes-parameter>`_"""
|
|
||||||
self.connectClient("chanop", name="chanop")
|
|
||||||
|
|
||||||
if "EXCEPTS" in self.server_support:
|
|
||||||
mode = self.server_support["EXCEPTS"] or "e"
|
|
||||||
if "CHANMODES" in self.server_support:
|
|
||||||
self.assertIn(
|
|
||||||
mode,
|
|
||||||
self.server_support["CHANMODES"],
|
|
||||||
fail_msg="ISUPPORT EXCEPTS is present, but '{item}' is missing "
|
|
||||||
"from 'CHANMODES={list}'",
|
|
||||||
)
|
|
||||||
self.assertIn(
|
|
||||||
mode,
|
|
||||||
self.server_support["CHANMODES"].split(",")[0],
|
|
||||||
fail_msg="ISUPPORT EXCEPTS is present, but '{item}' is not "
|
|
||||||
"in group A",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
mode = "e"
|
|
||||||
if "CHANMODES" in self.server_support:
|
|
||||||
if "e" not in self.server_support["CHANMODES"]:
|
|
||||||
raise runner.OptionalExtensionNotSupported(
|
|
||||||
"Ban exception (or mode letter is not +e)"
|
|
||||||
)
|
|
||||||
self.assertIn(
|
|
||||||
mode,
|
|
||||||
self.server_support["CHANMODES"].split(",")[0],
|
|
||||||
fail_msg="Mode +e (assumed to be ban exception) is present, "
|
|
||||||
"but 'e' is not in group A",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise runner.OptionalExtensionNotSupported("ISUPPORT CHANMODES")
|
|
||||||
|
|
||||||
self.sendLine("chanop", "JOIN #chan")
|
|
||||||
self.getMessages("chanop")
|
|
||||||
self.sendLine("chanop", "MODE #chan +b ba*!*@*")
|
|
||||||
self.getMessages("chanop")
|
|
||||||
|
|
||||||
# banned client cannot join
|
|
||||||
self.connectClient("Bar", name="bar")
|
|
||||||
self.sendLine("bar", "JOIN #chan")
|
|
||||||
self.assertMessageMatch(self.getMessage("bar"), command=ERR_BANNEDFROMCHAN)
|
|
||||||
|
|
||||||
# chanop sets exception
|
|
||||||
self.sendLine("chanop", "MODE #chan +e *ar!*@*")
|
|
||||||
self.assertMessageMatch(self.getMessage("chanop"), command="MODE")
|
|
||||||
|
|
||||||
# client can now join
|
|
||||||
self.sendLine("bar", "JOIN #chan")
|
|
||||||
self.assertMessageMatch(self.getMessage("bar"), command="JOIN")
|
|
||||||
|
|
||||||
# TODO: Add testBanExceptionList, once the numerics are specified in Modern
|
|
||||||
|
|
||||||
@cases.mark_specifications("Ergo")
|
@cases.mark_specifications("Ergo")
|
||||||
def testCaseInsensitive(self):
|
def testCaseInsensitive(self):
|
||||||
"""Some clients allow unsetting modes if their argument matches
|
"""Some clients allow unsetting modes if their argument matches
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
Various Ergo-specific channel modes
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import ERR_CANNOTSENDTOCHAN, ERR_CHANOPRIVSNEEDED
|
from irctest.numerics import ERR_CANNOTSENDTOCHAN, ERR_CHANOPRIVSNEEDED
|
||||||
|
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
"""
|
|
||||||
Channel key (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.3.1>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.3>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#key-channel-mode>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
@ -27,16 +20,10 @@ class KeyTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
self.connectClient("qux")
|
self.connectClient("qux")
|
||||||
self.getMessages(2)
|
self.getMessages(2)
|
||||||
# JOIN with a missing key MUST receive ERR_BADCHANNELKEY:
|
|
||||||
self.sendLine(2, "JOIN #chan")
|
self.sendLine(2, "JOIN #chan")
|
||||||
reply_cmds = {msg.command for msg in self.getMessages(2)}
|
reply = self.getMessages(2)
|
||||||
self.assertNotIn("JOIN", reply_cmds)
|
self.assertNotIn("JOIN", {msg.command for msg in reply})
|
||||||
self.assertIn(ERR_BADCHANNELKEY, reply_cmds)
|
self.assertIn(ERR_BADCHANNELKEY, {msg.command for msg in reply})
|
||||||
# similarly for JOIN with an incorrect key:
|
|
||||||
self.sendLine(2, "JOIN #chan bees")
|
|
||||||
reply_cmds = {msg.command for msg in self.getMessages(2)}
|
|
||||||
self.assertNotIn("JOIN", reply_cmds)
|
|
||||||
self.assertIn(ERR_BADCHANNELKEY, reply_cmds)
|
|
||||||
|
|
||||||
self.sendLine(2, "JOIN #chan beer")
|
self.sendLine(2, "JOIN #chan beer")
|
||||||
reply = self.getMessages(2)
|
reply = self.getMessages(2)
|
||||||
@ -70,21 +57,6 @@ class KeyTestCase(cases.BaseServerTestCase):
|
|||||||
-- https://modern.ircdocs.horse/#key-channel-mode
|
-- https://modern.ircdocs.horse/#key-channel-mode
|
||||||
-- https://github.com/ircdocs/modern-irc/pull/111
|
-- https://github.com/ircdocs/modern-irc/pull/111
|
||||||
"""
|
"""
|
||||||
if key == "" and self.controller.software_name in (
|
|
||||||
"ircu2",
|
|
||||||
"Nefarious",
|
|
||||||
"snircd",
|
|
||||||
):
|
|
||||||
pytest.xfail(
|
|
||||||
"ircu2 returns ERR_NEEDMOREPARAMS on empty keys: "
|
|
||||||
"https://github.com/UndernetIRC/ircu2/issues/13"
|
|
||||||
)
|
|
||||||
if (key == "" or " " in key) and self.controller.software_name == "ngIRCd":
|
|
||||||
pytest.xfail(
|
|
||||||
"ngIRCd does not validate channel keys: "
|
|
||||||
"https://github.com/ngircd/ngircd/issues/290"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.connectClient("bar")
|
self.connectClient("bar")
|
||||||
self.joinChannel(1, "#chan")
|
self.joinChannel(1, "#chan")
|
||||||
self.sendLine(1, f"MODE #chan +k :{key}")
|
self.sendLine(1, f"MODE #chan +k :{key}")
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
"""
|
|
||||||
Channel moderation mode (`RFC 2812
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.3>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#ban-channel-mode>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import ERR_CANNOTSENDTOCHAN
|
from irctest.numerics import ERR_CANNOTSENDTOCHAN
|
||||||
|
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
Mute extban, currently no specifications or ways to discover it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases, runner
|
from irctest import cases, runner
|
||||||
from irctest.numerics import ERR_CANNOTSENDTOCHAN, ERR_CHANOPRIVSNEEDED
|
from irctest.numerics import ERR_CANNOTSENDTOCHAN, ERR_CHANOPRIVSNEEDED
|
||||||
from irctest.patma import ANYLIST, StrRe
|
from irctest.patma import ANYLIST, StrRe
|
||||||
@ -198,7 +194,7 @@ class MuteExtbanTestCase(cases.BaseServerTestCase):
|
|||||||
self.getMessages(client)
|
self.getMessages(client)
|
||||||
|
|
||||||
# +e grants an exemption to +b
|
# +e grants an exemption to +b
|
||||||
self.sendLine("chanop", f"MODE #chan +e {prefix}{self.char()}:*!*evan@*")
|
self.sendLine("chanop", f"MODE #chan +e {prefix}{self.char()}:*!~evan@*")
|
||||||
replies = {msg.command for msg in self.getMessages("chanop")}
|
replies = {msg.command for msg in self.getMessages("chanop")}
|
||||||
self.assertIn("MODE", replies)
|
self.assertIn("MODE", replies)
|
||||||
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
|
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
|
||||||
|
69
irctest/server_tests/chmodes/noctcp.py
Normal file
69
irctest/server_tests/chmodes/noctcp.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from irctest import cases, runner
|
||||||
|
from irctest.numerics import ERR_CANNOTSENDTOCHAN
|
||||||
|
from irctest.patma import ANYSTR
|
||||||
|
|
||||||
|
|
||||||
|
class NoctcpModeTestCase(cases.BaseServerTestCase):
|
||||||
|
@cases.mark_specifications("Modern")
|
||||||
|
def testNoctcpMode(self):
|
||||||
|
"""
|
||||||
|
"This mode is used in almost all IRC software today. The standard mode letter
|
||||||
|
used for it is `"+C"`.
|
||||||
|
When this mode is set, should not send [CTCP](/ctcp.html) messages, except
|
||||||
|
CTCP Action (also known as `/me`) to the channel.
|
||||||
|
When blocking a message because of this mode, servers SHOULD use
|
||||||
|
ERR_CANNOTSENDTOCHAN"
|
||||||
|
-- TODO add link
|
||||||
|
"""
|
||||||
|
self.connectClient("chanop")
|
||||||
|
|
||||||
|
if "C" not in self.server_support.get("CHANMODES", ""):
|
||||||
|
raise runner.NotImplementedByController("+C (noctcp) channel mode")
|
||||||
|
|
||||||
|
# Both users join:
|
||||||
|
|
||||||
|
self.sendLine(1, "JOIN #chan")
|
||||||
|
self.getMessages(1) # synchronize
|
||||||
|
|
||||||
|
self.connectClient("user")
|
||||||
|
self.sendLine(2, "JOIN #chan")
|
||||||
|
self.getMessages(2)
|
||||||
|
self.getMessages(1)
|
||||||
|
|
||||||
|
# Send ACTION and PING, both should go through:
|
||||||
|
|
||||||
|
self.sendLine(2, "PRIVMSG #chan :\x01ACTION is testing\x01")
|
||||||
|
self.sendLine(2, "PRIVMSG #chan :\x01PING 12345\x01")
|
||||||
|
self.assertEqual(self.getMessages(2), [])
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[(m.command, m.params[1]) for m in self.getMessages(1)],
|
||||||
|
[
|
||||||
|
("PRIVMSG", "\x01ACTION is testing\x01"),
|
||||||
|
("PRIVMSG", "\x01PING 12345\x01"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set mode +C:
|
||||||
|
|
||||||
|
self.sendLine(1, "MODE #chan +C")
|
||||||
|
self.getMessages(1)
|
||||||
|
self.getMessages(2)
|
||||||
|
|
||||||
|
# Send ACTION and PING, only ACTION should go through:
|
||||||
|
|
||||||
|
self.sendLine(2, "PRIVMSG #chan :\x01ACTION is testing\x01")
|
||||||
|
self.assertEqual(self.getMessages(2), [])
|
||||||
|
self.sendLine(2, "PRIVMSG #chan :\x01PING 12345\x01")
|
||||||
|
self.assertMessageMatch(
|
||||||
|
self.getMessage(2),
|
||||||
|
command=ERR_CANNOTSENDTOCHAN,
|
||||||
|
params=["user", "#chan", ANYSTR],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[(m.command, m.params[1]) for m in self.getMessages(1)],
|
||||||
|
[
|
||||||
|
("PRIVMSG", "\x01ACTION is testing\x01"),
|
||||||
|
],
|
||||||
|
)
|
@ -1,10 +1,3 @@
|
|||||||
"""
|
|
||||||
Channel secrecy mode (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.3.1>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.3>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#secret-channel-mode>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import RPL_LIST
|
from irctest.numerics import RPL_LIST
|
||||||
|
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
"""
|
|
||||||
`Ergo <https://ergo.chat/>`_-specific tests for nick collisions based on Unicode
|
|
||||||
confusable characters
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import ERR_NICKNAMEINUSE, RPL_WELCOME
|
from irctest.numerics import ERR_NICKNAMEINUSE, RPL_WELCOME
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
Tests section 4.1 of RFC 1459.
|
Tests section 4.1 of RFC 1459.
|
||||||
<https://tools.ietf.org/html/rfc1459#section-4.1>
|
<https://tools.ietf.org/html/rfc1459#section-4.1>
|
||||||
|
|
||||||
TODO: cross-reference Modern and RFC 2812 too
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.client_mock import ConnectionClosed
|
from irctest.client_mock import ConnectionClosed
|
||||||
from irctest.numerics import ERR_NEEDMOREPARAMS, ERR_PASSWDMISMATCH
|
from irctest.numerics import ERR_NEEDMOREPARAMS
|
||||||
from irctest.patma import ANYSTR, StrRe
|
from irctest.patma import ANYSTR, StrRe
|
||||||
|
|
||||||
|
|
||||||
@ -38,14 +36,8 @@ class PasswordedConnectionRegistrationTestCase(cases.BaseServerTestCase):
|
|||||||
m.command, "001", msg="Got 001 after NICK+USER but missing PASS"
|
m.command, "001", msg="Got 001 after NICK+USER but missing PASS"
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
def testWrongPassword(self):
|
def testWrongPassword(self):
|
||||||
"""
|
|
||||||
"If the password supplied does not match the password expected by the server,
|
|
||||||
then the server SHOULD send ERR_PASSWDMISMATCH and MUST close the connection
|
|
||||||
with ERROR."
|
|
||||||
-- https://github.com/ircdocs/modern-irc/pull/172
|
|
||||||
"""
|
|
||||||
self.addClient()
|
self.addClient()
|
||||||
self.sendLine(1, "PASS {}".format(self.password + "garbage"))
|
self.sendLine(1, "PASS {}".format(self.password + "garbage"))
|
||||||
self.sendLine(1, "NICK foo")
|
self.sendLine(1, "NICK foo")
|
||||||
@ -54,13 +46,6 @@ class PasswordedConnectionRegistrationTestCase(cases.BaseServerTestCase):
|
|||||||
self.assertNotEqual(
|
self.assertNotEqual(
|
||||||
m.command, "001", msg="Got 001 after NICK+USER but incorrect PASS"
|
m.command, "001", msg="Got 001 after NICK+USER but incorrect PASS"
|
||||||
)
|
)
|
||||||
self.assertIn(m.command, {ERR_PASSWDMISMATCH, "ERROR"})
|
|
||||||
|
|
||||||
if m.command == "ERR_PASSWDMISMATCH":
|
|
||||||
m = self.getRegistrationMessage(1)
|
|
||||||
self.assertEqual(
|
|
||||||
m.command, "ERROR", msg="ERR_PASSWDMISMATCH not followed by ERROR."
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", strict=True)
|
@cases.mark_specifications("RFC1459", "RFC2812", strict=True)
|
||||||
def testPassAfterNickuser(self):
|
def testPassAfterNickuser(self):
|
||||||
@ -97,10 +82,6 @@ class ConnectionRegistrationTestCase(cases.BaseServerTestCase):
|
|||||||
self.getMessages(1)
|
self.getMessages(1)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC2812")
|
@cases.mark_specifications("RFC2812")
|
||||||
@cases.xfailIfSoftware(["Charybdis", "Solanum"], "very flaky")
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["ircu2", "Nefarious", "snircd"], "ircu2 does not send ERROR"
|
|
||||||
)
|
|
||||||
def testQuitErrors(self):
|
def testQuitErrors(self):
|
||||||
"""“A client session is terminated with a quit message. The server
|
"""“A client session is terminated with a quit message. The server
|
||||||
acknowledges this by sending an ERROR message to the client.”
|
acknowledges this by sending an ERROR message to the client.”
|
||||||
@ -181,10 +162,6 @@ class ConnectionRegistrationTestCase(cases.BaseServerTestCase):
|
|||||||
"neither got 001.",
|
"neither got 001.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["ircu2", "Nefarious", "ngIRCd"],
|
|
||||||
"uses a default value instead of ERR_NEEDMOREPARAMS",
|
|
||||||
)
|
|
||||||
def testEmptyRealname(self):
|
def testEmptyRealname(self):
|
||||||
"""
|
"""
|
||||||
Syntax:
|
Syntax:
|
||||||
@ -206,3 +183,60 @@ class ConnectionRegistrationTestCase(cases.BaseServerTestCase):
|
|||||||
command=ERR_NEEDMOREPARAMS,
|
command=ERR_NEEDMOREPARAMS,
|
||||||
params=[StrRe(r"(\*|foo)"), "USER", ANYSTR],
|
params=[StrRe(r"(\*|foo)"), "USER", ANYSTR],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cases.mark_specifications("IRCv3")
|
||||||
|
def testIrc301CapLs(self):
|
||||||
|
"""
|
||||||
|
Current version:
|
||||||
|
|
||||||
|
"The LS subcommand is used to list the capabilities supported by the server.
|
||||||
|
The client should send an LS subcommand with no other arguments to solicit
|
||||||
|
a list of all capabilities."
|
||||||
|
|
||||||
|
"If a client has not indicated support for CAP LS 302 features,
|
||||||
|
the server MUST NOT send these new features to the client."
|
||||||
|
-- <https://ircv3.net/specs/core/capability-negotiation.html>
|
||||||
|
|
||||||
|
Before the v3.1 / v3.2 merge:
|
||||||
|
|
||||||
|
IRCv3.1: “The LS subcommand is used to list the capabilities
|
||||||
|
supported by the server. The client should send an LS subcommand with
|
||||||
|
no other arguments to solicit a list of all capabilities.”
|
||||||
|
-- <http://ircv3.net/specs/core/capability-negotiation-3.1.html#the-cap-ls-subcommand>
|
||||||
|
|
||||||
|
IRCv3.2: “Servers MUST NOT send messages described by this document if
|
||||||
|
the client only supports version 3.1.”
|
||||||
|
-- <http://ircv3.net/specs/core/capability-negotiation-3.2.html#version-in-cap-ls>
|
||||||
|
""" # noqa
|
||||||
|
self.addClient()
|
||||||
|
self.sendLine(1, "CAP LS")
|
||||||
|
m = self.getRegistrationMessage(1)
|
||||||
|
self.assertNotEqual(
|
||||||
|
m.params[2],
|
||||||
|
"*",
|
||||||
|
m,
|
||||||
|
fail_msg="Server replied with multi-line CAP LS to a "
|
||||||
|
"“CAP LS” (ie. IRCv3.1) request: {msg}",
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
any("=" in cap for cap in m.params[2].split()),
|
||||||
|
"Server replied with a name-value capability in "
|
||||||
|
"CAP LS reply as a response to “CAP LS” (ie. IRCv3.1) "
|
||||||
|
"request: {}".format(m),
|
||||||
|
)
|
||||||
|
|
||||||
|
@cases.mark_specifications("IRCv3")
|
||||||
|
def testEmptyCapList(self):
|
||||||
|
"""“If no capabilities are active, an empty parameter must be sent.”
|
||||||
|
-- <http://ircv3.net/specs/core/capability-negotiation-3.1.html#the-cap-list-subcommand>
|
||||||
|
""" # noqa
|
||||||
|
self.addClient()
|
||||||
|
self.sendLine(1, "CAP LIST")
|
||||||
|
m = self.getRegistrationMessage(1)
|
||||||
|
self.assertMessageMatch(
|
||||||
|
m,
|
||||||
|
command="CAP",
|
||||||
|
params=["*", "LIST", ""],
|
||||||
|
fail_msg="Sending “CAP LIST” as first message got a reply "
|
||||||
|
"that is not “CAP * LIST :”: {msg}",
|
||||||
|
)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 echo-message <https://ircv3.net/specs/extensions/echo-message>`_
|
<http://ircv3.net/specs/extensions/echo-message-3.2.html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
from irctest.basecontrollers import NotImplementedByController
|
||||||
from irctest.irc_utils.junkdrawer import random_name
|
from irctest.irc_utils.junkdrawer import random_name
|
||||||
from irctest.patma import ANYDICT
|
from irctest.patma import ANYDICT
|
||||||
|
|
||||||
@ -22,18 +23,31 @@ class EchoMessageTestCase(cases.BaseServerTestCase):
|
|||||||
@cases.mark_capabilities("echo-message")
|
@cases.mark_capabilities("echo-message")
|
||||||
def testEchoMessage(self, command, solo, server_time):
|
def testEchoMessage(self, command, solo, server_time):
|
||||||
"""<http://ircv3.net/specs/extensions/echo-message-3.2.html>"""
|
"""<http://ircv3.net/specs/extensions/echo-message-3.2.html>"""
|
||||||
if server_time:
|
self.addClient()
|
||||||
self.connectClient(
|
self.sendLine(1, "CAP LS 302")
|
||||||
"baz",
|
capabilities = self.getCapLs(1)
|
||||||
capabilities=["echo-message", "server-time"],
|
if "echo-message" not in capabilities:
|
||||||
skip_if_cap_nak=True,
|
raise NotImplementedByController("echo-message")
|
||||||
)
|
if server_time and "server-time" not in capabilities:
|
||||||
else:
|
raise NotImplementedByController("server-time")
|
||||||
self.connectClient(
|
|
||||||
"baz",
|
# TODO: check also without this
|
||||||
capabilities=["echo-message", "server-time"],
|
self.sendLine(
|
||||||
skip_if_cap_nak=True,
|
1,
|
||||||
)
|
"CAP REQ :echo-message{}".format(" server-time" if server_time else ""),
|
||||||
|
)
|
||||||
|
self.getRegistrationMessage(1)
|
||||||
|
# TODO: Remove this one the trailing space issue is fixed in Charybdis
|
||||||
|
# and Mammon:
|
||||||
|
# self.assertMessageMatch(m, command='CAP',
|
||||||
|
# params=['*', 'ACK', 'echo-message'] +
|
||||||
|
# (['server-time'] if server_time else []),
|
||||||
|
# fail_msg='Did not ACK advertised capabilities: {msg}')
|
||||||
|
self.sendLine(1, "USER f * * :foo")
|
||||||
|
self.sendLine(1, "NICK baz")
|
||||||
|
self.sendLine(1, "CAP END")
|
||||||
|
self.skipToWelcome(1)
|
||||||
|
self.getMessages(1)
|
||||||
|
|
||||||
self.sendLine(1, "JOIN #chan")
|
self.sendLine(1, "JOIN #chan")
|
||||||
|
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
`Ergo <https://ergo.chat/>`-specific tests of NickServ.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import RPL_YOUREOPER
|
from irctest.numerics import RPL_YOUREOPER
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 extended-join <https://ircv3.net/specs/extensions/extended-join>`_
|
<http://ircv3.net/specs/extensions/extended-join-3.1.html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
|
||||||
|
|
||||||
@cases.mark_services
|
@cases.mark_services
|
||||||
class MetadataTestCase(cases.BaseServerTestCase):
|
class MetadataTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
def connectRegisteredClient(self, nick):
|
def connectRegisteredClient(self, nick):
|
||||||
self.addClient()
|
self.addClient()
|
||||||
self.sendLine(2, "CAP LS 302")
|
self.sendLine(2, "CAP LS 302")
|
||||||
@ -50,7 +50,7 @@ class MetadataTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_capabilities("extended-join")
|
@cases.mark_capabilities("extended-join")
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testLoggedIn(self):
|
def testLoggedIn(self):
|
||||||
self.connectClient("foo", capabilities=["extended-join"], skip_if_cap_nak=True)
|
self.connectClient("foo", capabilities=["extended-join"], skip_if_cap_nak=True)
|
||||||
self.joinChannel(1, "#chan")
|
self.joinChannel(1, "#chan")
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
The HELP and HELPOP command (`Modern <https://modern.ircdocs.horse/#help-message>`__)
|
The HELP and HELPOP command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import functools
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -18,30 +17,6 @@ from irctest.numerics import (
|
|||||||
from irctest.patma import ANYSTR, StrRe
|
from irctest.patma import ANYSTR, StrRe
|
||||||
|
|
||||||
|
|
||||||
def with_xfails(f):
|
|
||||||
@functools.wraps(f)
|
|
||||||
def newf(self, command, *args, **kwargs):
|
|
||||||
if command == "HELP" and self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.ImplementationChoice(
|
|
||||||
"fail because Bahamut forwards /HELP to HelpServ (but not /HELPOP)"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.controller.software_name in ("irc2", "ircu2", "ngIRCd"):
|
|
||||||
raise runner.ImplementationChoice(
|
|
||||||
"numerics in reply to /HELP and /HELPOP (uses NOTICE instead)"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.controller.software_name == "UnrealIRCd":
|
|
||||||
raise runner.ImplementationChoice(
|
|
||||||
"fails because Unreal uses custom numerics "
|
|
||||||
"https://github.com/unrealircd/unrealircd/pull/184"
|
|
||||||
)
|
|
||||||
|
|
||||||
return f(self, command, *args, **kwargs)
|
|
||||||
|
|
||||||
return newf
|
|
||||||
|
|
||||||
|
|
||||||
class HelpTestCase(cases.BaseServerTestCase):
|
class HelpTestCase(cases.BaseServerTestCase):
|
||||||
def _assertValidHelp(self, messages, subject):
|
def _assertValidHelp(self, messages, subject):
|
||||||
if subject != ANYSTR:
|
if subject != ANYSTR:
|
||||||
@ -71,7 +46,6 @@ class HelpTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
@pytest.mark.parametrize("command", ["HELP", "HELPOP"])
|
@pytest.mark.parametrize("command", ["HELP", "HELPOP"])
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
@with_xfails
|
|
||||||
def testHelpNoArg(self, command):
|
def testHelpNoArg(self, command):
|
||||||
self.connectClient("nick")
|
self.connectClient("nick")
|
||||||
self.sendLine(1, f"{command}")
|
self.sendLine(1, f"{command}")
|
||||||
@ -85,7 +59,6 @@ class HelpTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
@pytest.mark.parametrize("command", ["HELP", "HELPOP"])
|
@pytest.mark.parametrize("command", ["HELP", "HELPOP"])
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
@with_xfails
|
|
||||||
def testHelpPrivmsg(self, command):
|
def testHelpPrivmsg(self, command):
|
||||||
self.connectClient("nick")
|
self.connectClient("nick")
|
||||||
self.sendLine(1, f"{command} PRIVMSG")
|
self.sendLine(1, f"{command} PRIVMSG")
|
||||||
@ -98,7 +71,6 @@ class HelpTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
@pytest.mark.parametrize("command", ["HELP", "HELPOP"])
|
@pytest.mark.parametrize("command", ["HELP", "HELPOP"])
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
@with_xfails
|
|
||||||
def testHelpUnknownSubject(self, command):
|
def testHelpUnknownSubject(self, command):
|
||||||
self.connectClient("nick")
|
self.connectClient("nick")
|
||||||
self.sendLine(1, f"{command} THISISNOTACOMMAND")
|
self.sendLine(1, f"{command} THISISNOTACOMMAND")
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
The INFO command (`RFC 1459
|
The INFO command.
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.3.8>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.4.10>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#info-message>`__)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -87,9 +84,6 @@ class InfoTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
@pytest.mark.parametrize("target", ["invalid.server.example", "invalidserver"])
|
@pytest.mark.parametrize("target", ["invalid.server.example", "invalidserver"])
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", deprecated=True)
|
@cases.mark_specifications("RFC1459", "RFC2812", deprecated=True)
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Ergo"], "does not apply to Ergo, which ignores the optional <target> argument"
|
|
||||||
)
|
|
||||||
def testInfoNosuchserver(self, target):
|
def testInfoNosuchserver(self, target):
|
||||||
"""
|
"""
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.3.8>
|
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.3.8>
|
||||||
|
@ -1,18 +1,10 @@
|
|||||||
"""
|
|
||||||
The INVITE command (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.7>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.7>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#invite-message>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from irctest import cases, runner
|
from irctest import cases
|
||||||
from irctest.numerics import (
|
from irctest.numerics import (
|
||||||
ERR_BANNEDFROMCHAN,
|
ERR_BANNEDFROMCHAN,
|
||||||
ERR_CHANOPRIVSNEEDED,
|
ERR_CHANOPRIVSNEEDED,
|
||||||
ERR_INVITEONLYCHAN,
|
ERR_INVITEONLYCHAN,
|
||||||
ERR_NEEDMOREPARAMS,
|
|
||||||
ERR_NOSUCHNICK,
|
ERR_NOSUCHNICK,
|
||||||
ERR_NOTONCHANNEL,
|
ERR_NOTONCHANNEL,
|
||||||
ERR_USERONCHANNEL,
|
ERR_USERONCHANNEL,
|
||||||
@ -201,9 +193,6 @@ class InviteTestCase(cases.BaseServerTestCase):
|
|||||||
self._testInvite(opped=True, invite_only=invite_only)
|
self._testInvite(opped=True, invite_only=invite_only)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern", strict=True)
|
@cases.mark_specifications("RFC1459", "RFC2812", "Modern", strict=True)
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Hybrid", "Plexus4"], "the only strict test that Hybrid fails"
|
|
||||||
)
|
|
||||||
def testInviteUnopped(self):
|
def testInviteUnopped(self):
|
||||||
"""Tests invites from unopped users on not-invite-only chans."""
|
"""Tests invites from unopped users on not-invite-only chans."""
|
||||||
self._testInvite(opped=False, invite_only=False)
|
self._testInvite(opped=False, invite_only=False)
|
||||||
@ -241,11 +230,6 @@ class InviteTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Plexus4"],
|
|
||||||
"Plexus4 allows non-op to invite if (and only if) the channel is not "
|
|
||||||
"invite-only",
|
|
||||||
)
|
|
||||||
def testInviteInviteOnly(self):
|
def testInviteInviteOnly(self):
|
||||||
"""
|
"""
|
||||||
"To invite a user to a channel which is invite only (MODE
|
"To invite a user to a channel which is invite only (MODE
|
||||||
@ -373,87 +357,6 @@ class InviteTestCase(cases.BaseServerTestCase):
|
|||||||
params=["foo", "bar", "#chan", ANYSTR],
|
params=["foo", "bar", "#chan", ANYSTR],
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC2812", "Modern")
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["ircu2"],
|
|
||||||
"Uses 346/347 instead of 336/337 to reply to INVITE "
|
|
||||||
"https://github.com/UndernetIRC/ircu2/pull/20",
|
|
||||||
)
|
|
||||||
def testInviteList(self):
|
|
||||||
self.connectClient("foo")
|
|
||||||
self.connectClient("bar")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.getMessages(2)
|
|
||||||
|
|
||||||
self.sendLine(1, "JOIN #chan")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(1, "INVITE bar #chan")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.getMessages(2)
|
|
||||||
|
|
||||||
self.sendLine(2, "INVITE")
|
|
||||||
m = self.getMessage(2)
|
|
||||||
if m.command == ERR_NEEDMOREPARAMS:
|
|
||||||
raise runner.OptionalExtensionNotSupported("INVITE with no parameter")
|
|
||||||
if m.command != "337":
|
|
||||||
# Hybrid always sends an empty list; so skip this.
|
|
||||||
self.assertMessageMatch(
|
|
||||||
m,
|
|
||||||
command="336",
|
|
||||||
params=["bar", "#chan"],
|
|
||||||
)
|
|
||||||
m = self.getMessage(2)
|
|
||||||
self.assertMessageMatch(
|
|
||||||
m,
|
|
||||||
command="337",
|
|
||||||
params=["bar", ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_isupport("INVEX")
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
def testInvexList(self):
|
|
||||||
self.connectClient("foo")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
if "INVEX" in self.server_support:
|
|
||||||
invex = self.server_support.get("INVEX") or "I"
|
|
||||||
else:
|
|
||||||
raise runner.IsupportTokenNotSupported("INVEX")
|
|
||||||
|
|
||||||
self.sendLine(1, "JOIN #chan")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(1, f"MODE #chan +{invex} bar!*@*")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(1, f"MODE #chan +{invex}")
|
|
||||||
m = self.getMessage(1)
|
|
||||||
if len(m.params) == 3:
|
|
||||||
# Old format
|
|
||||||
self.assertMessageMatch(
|
|
||||||
m,
|
|
||||||
command="346",
|
|
||||||
params=["foo", "#chan", "bar!*@*"],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.assertMessageMatch(
|
|
||||||
m,
|
|
||||||
command="346",
|
|
||||||
params=[
|
|
||||||
"foo",
|
|
||||||
"#chan",
|
|
||||||
"bar!*@*",
|
|
||||||
StrRe("foo(!.*@.*)?"),
|
|
||||||
StrRe("[0-9]+"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command="347",
|
|
||||||
params=["foo", "#chan", ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("Ergo")
|
@cases.mark_specifications("Ergo")
|
||||||
def testInviteExemptsFromBan(self):
|
def testInviteExemptsFromBan(self):
|
||||||
# regression test for ergochat/ergo#1876;
|
# regression test for ergochat/ergo#1876;
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
"""
|
|
||||||
RPL_ISUPPORT: `format <https://modern.ircdocs.horse/#rplisupport-005>`__
|
|
||||||
and various `tokens <https://modern.ircdocs.horse/#rplisupport-parameters>`__
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from irctest import cases, runner
|
from irctest import cases, runner
|
||||||
@ -16,7 +11,7 @@ class IsupportTestCase(cases.BaseServerTestCase):
|
|||||||
self.connectClient("foo")
|
self.connectClient("foo")
|
||||||
|
|
||||||
if "PREFIX" not in self.server_support:
|
if "PREFIX" not in self.server_support:
|
||||||
raise runner.IsupportTokenNotSupported("PREFIX")
|
raise runner.NotImplementedByController("PREFIX")
|
||||||
|
|
||||||
if self.server_support["PREFIX"] == "":
|
if self.server_support["PREFIX"] == "":
|
||||||
# "The value is OPTIONAL and when it is not specified indicates that no
|
# "The value is OPTIONAL and when it is not specified indicates that no
|
||||||
@ -80,7 +75,7 @@ class IsupportTestCase(cases.BaseServerTestCase):
|
|||||||
self.connectClient("foo")
|
self.connectClient("foo")
|
||||||
|
|
||||||
if "TARGMAX" not in self.server_support:
|
if "TARGMAX" not in self.server_support:
|
||||||
raise runner.IsupportTokenNotSupported("TARGMAX")
|
raise runner.NotImplementedByController("TARGMAX")
|
||||||
|
|
||||||
parts = self.server_support["TARGMAX"].split(",")
|
parts = self.server_support["TARGMAX"].split(",")
|
||||||
for part in parts:
|
for part in parts:
|
||||||
|
@ -1,30 +1,5 @@
|
|||||||
"""
|
from irctest import cases
|
||||||
The JOIN command (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.1>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.1>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#join-message>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases, runner
|
|
||||||
from irctest.irc_utils import ambiguities
|
from irctest.irc_utils import ambiguities
|
||||||
from irctest.numerics import (
|
|
||||||
ERR_BADCHANMASK,
|
|
||||||
ERR_FORBIDDENCHANNEL,
|
|
||||||
ERR_NOSUCHCHANNEL,
|
|
||||||
RPL_ENDOFNAMES,
|
|
||||||
RPL_NAMREPLY,
|
|
||||||
)
|
|
||||||
from irctest.patma import ANYSTR, StrRe
|
|
||||||
|
|
||||||
ERR_BADCHANNAME = "479" # Hybrid only, and conflicts with others
|
|
||||||
|
|
||||||
|
|
||||||
JOIN_ERROR_NUMERICS = {
|
|
||||||
ERR_BADCHANMASK,
|
|
||||||
ERR_NOSUCHCHANNEL,
|
|
||||||
ERR_FORBIDDENCHANNEL,
|
|
||||||
ERR_BADCHANNAME,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class JoinTestCase(cases.BaseServerTestCase):
|
class JoinTestCase(cases.BaseServerTestCase):
|
||||||
@ -44,22 +19,13 @@ class JoinTestCase(cases.BaseServerTestCase):
|
|||||||
self.connectClient("foo")
|
self.connectClient("foo")
|
||||||
self.sendLine(1, "JOIN #chan")
|
self.sendLine(1, "JOIN #chan")
|
||||||
received_commands = {m.command for m in self.getMessages(1)}
|
received_commands = {m.command for m in self.getMessages(1)}
|
||||||
expected_commands = {RPL_NAMREPLY, RPL_ENDOFNAMES, "JOIN"}
|
expected_commands = {"353", "366"} # RPL_NAMREPLY # RPL_ENDOFNAMES
|
||||||
acceptable_commands = expected_commands | {"MODE"}
|
self.assertTrue(
|
||||||
self.assertLessEqual( # set inclusion
|
expected_commands.issubset(received_commands),
|
||||||
expected_commands,
|
|
||||||
received_commands,
|
|
||||||
"Server sent {} commands, but at least {} were expected.".format(
|
"Server sent {} commands, but at least {} were expected.".format(
|
||||||
received_commands, expected_commands
|
received_commands, expected_commands
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertLessEqual( # ditto
|
|
||||||
received_commands,
|
|
||||||
acceptable_commands,
|
|
||||||
"Server sent {} commands, but only {} were expected.".format(
|
|
||||||
received_commands, acceptable_commands
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC2812")
|
@cases.mark_specifications("RFC2812")
|
||||||
def testJoinNamreply(self):
|
def testJoinNamreply(self):
|
||||||
@ -144,95 +110,3 @@ class JoinTestCase(cases.BaseServerTestCase):
|
|||||||
'"foo" with an optional "+" or "@" prefix, but got: '
|
'"foo" with an optional "+" or "@" prefix, but got: '
|
||||||
"{msg}",
|
"{msg}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def testJoinPartiallyInvalid(self):
|
|
||||||
"""TODO: specify this in Modern"""
|
|
||||||
self.connectClient("foo")
|
|
||||||
if int(self.targmax.get("JOIN") or "4") < 2:
|
|
||||||
raise runner.OptionalExtensionNotSupported("multi-channel JOIN")
|
|
||||||
|
|
||||||
self.sendLine(1, "JOIN #valid,inv@lid")
|
|
||||||
messages = self.getMessages(1)
|
|
||||||
received_commands = {m.command for m in messages}
|
|
||||||
expected_commands = {RPL_NAMREPLY, RPL_ENDOFNAMES, "JOIN"}
|
|
||||||
acceptable_commands = expected_commands | JOIN_ERROR_NUMERICS | {"MODE"}
|
|
||||||
self.assertLessEqual(
|
|
||||||
expected_commands,
|
|
||||||
received_commands,
|
|
||||||
"Server sent {} commands, but at least {} were expected.".format(
|
|
||||||
received_commands, expected_commands
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.assertLessEqual(
|
|
||||||
received_commands,
|
|
||||||
acceptable_commands,
|
|
||||||
"Server sent {} commands, but only {} were expected.".format(
|
|
||||||
received_commands, acceptable_commands
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
nb_errors = 0
|
|
||||||
for m in messages:
|
|
||||||
if m.command in JOIN_ERROR_NUMERICS:
|
|
||||||
nb_errors += 1
|
|
||||||
self.assertMessageMatch(m, params=["foo", "inv@lid", ANYSTR])
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
nb_errors,
|
|
||||||
1,
|
|
||||||
fail_msg="Expected 1 error when joining channels '#valid' and 'inv@lid', "
|
|
||||||
"got {got}",
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_capabilities("batch", "labeled-response")
|
|
||||||
def testJoinPartiallyInvalidLabeledResponse(self):
|
|
||||||
"""TODO: specify this in Modern"""
|
|
||||||
self.connectClient(
|
|
||||||
"foo", capabilities=["batch", "labeled-response"], skip_if_cap_nak=True
|
|
||||||
)
|
|
||||||
if int(self.targmax.get("JOIN") or "4") < 2:
|
|
||||||
raise runner.OptionalExtensionNotSupported("multi-channel JOIN")
|
|
||||||
|
|
||||||
self.sendLine(1, "@label=label1 JOIN #valid,inv@lid")
|
|
||||||
messages = self.getMessages(1)
|
|
||||||
|
|
||||||
first_msg = messages.pop(0)
|
|
||||||
last_msg = messages.pop(-1)
|
|
||||||
self.assertMessageMatch(
|
|
||||||
first_msg, command="BATCH", params=[StrRe(r"\+.*"), "labeled-response"]
|
|
||||||
)
|
|
||||||
batch_id = first_msg.params[0][1:]
|
|
||||||
self.assertMessageMatch(last_msg, command="BATCH", params=["-" + batch_id])
|
|
||||||
|
|
||||||
received_commands = {m.command for m in messages}
|
|
||||||
expected_commands = {RPL_NAMREPLY, RPL_ENDOFNAMES, "JOIN"}
|
|
||||||
acceptable_commands = expected_commands | JOIN_ERROR_NUMERICS | {"MODE"}
|
|
||||||
self.assertLessEqual(
|
|
||||||
expected_commands,
|
|
||||||
received_commands,
|
|
||||||
"Server sent {} commands, but at least {} were expected.".format(
|
|
||||||
received_commands, expected_commands
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.assertLessEqual(
|
|
||||||
received_commands,
|
|
||||||
acceptable_commands,
|
|
||||||
"Server sent {} commands, but only {} were expected.".format(
|
|
||||||
received_commands, acceptable_commands
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
nb_errors = 0
|
|
||||||
for m in messages:
|
|
||||||
self.assertIn("batch", m.tags)
|
|
||||||
self.assertEqual(m.tags["batch"], batch_id)
|
|
||||||
if m.command in JOIN_ERROR_NUMERICS:
|
|
||||||
nb_errors += 1
|
|
||||||
self.assertMessageMatch(m, params=["foo", "inv@lid", ANYSTR])
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
nb_errors,
|
|
||||||
1,
|
|
||||||
fail_msg="Expected 1 error when joining channels '#valid' and 'inv@lid', "
|
|
||||||
"got {got}",
|
|
||||||
)
|
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
"""
|
|
||||||
The KICK command (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.1>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#kick-message>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from irctest import cases, client_mock, runner
|
from irctest import cases, client_mock, runner
|
||||||
@ -96,10 +89,6 @@ class KickTestCase(cases.BaseServerTestCase):
|
|||||||
self.assertMessageMatch(m3, command="KICK", params=["#chan", "bar", ANYSTR])
|
self.assertMessageMatch(m3, command="KICK", params=["#chan", "bar", ANYSTR])
|
||||||
|
|
||||||
@cases.mark_specifications("RFC2812")
|
@cases.mark_specifications("RFC2812")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Charybdis", "ircu2", "irc2", "Solanum"],
|
|
||||||
"uses the nick of the kickee rather than the kicker.",
|
|
||||||
)
|
|
||||||
def testKickDefaultComment(self):
|
def testKickDefaultComment(self):
|
||||||
"""
|
"""
|
||||||
"If a "comment" is
|
"If a "comment" is
|
||||||
@ -230,8 +219,13 @@ class KickTestCase(cases.BaseServerTestCase):
|
|||||||
self.connectClient("qux")
|
self.connectClient("qux")
|
||||||
self.joinChannel(4, "#chan")
|
self.joinChannel(4, "#chan")
|
||||||
|
|
||||||
if self.targmax.get("KICK", "1") == "1":
|
targmax = dict(
|
||||||
raise runner.OptionalExtensionNotSupported("Multi-target KICK")
|
item.split(":", 1)
|
||||||
|
for item in self.server_support.get("TARGMAX", "").split(",")
|
||||||
|
if item
|
||||||
|
)
|
||||||
|
if targmax.get("KICK", "1") == "1":
|
||||||
|
raise runner.NotImplementedByController("Multi-target KICK")
|
||||||
|
|
||||||
# TODO: check foo is an operator
|
# TODO: check foo is an operator
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 labeled-response <https://ircv3.net/specs/extensions/labeled-response>`_
|
|
||||||
|
|
||||||
This specification is a little hard to test because all labels are optional;
|
This specification is a little hard to test because all labels are optional;
|
||||||
so there may be many false positives.
|
so there may be many false positives.
|
||||||
|
|
||||||
|
<https://ircv3.net/specs/extensions/labeled-response.html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@ -14,7 +14,7 @@ from irctest.numerics import ERR_UNKNOWNCOMMAND
|
|||||||
from irctest.patma import ANYDICT, ANYOPTSTR, NotStrRe, RemainingKeys, StrRe
|
from irctest.patma import ANYDICT, ANYOPTSTR, NotStrRe, RemainingKeys, StrRe
|
||||||
|
|
||||||
|
|
||||||
class LabeledResponsesTestCase(cases.BaseServerTestCase):
|
class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
|
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
|
||||||
def testLabeledPrivmsgResponsesToMultipleClients(self):
|
def testLabeledPrivmsgResponsesToMultipleClients(self):
|
||||||
self.connectClient(
|
self.connectClient(
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
from irctest import cases, runner
|
|
||||||
from irctest.numerics import ERR_UNKNOWNCOMMAND, RPL_ENDOFLINKS, RPL_LINKS
|
|
||||||
from irctest.patma import ANYSTR, StrRe
|
|
||||||
|
|
||||||
|
|
||||||
class LinksTestCase(cases.BaseServerTestCase):
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
|
||||||
def testLinksSingleServer(self):
|
|
||||||
"""
|
|
||||||
Only testing the parameter-less case.
|
|
||||||
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc1459#section-4.3.3
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc2812#section-3.4.5
|
|
||||||
https://github.com/ircdocs/modern-irc/pull/175
|
|
||||||
|
|
||||||
"
|
|
||||||
364 RPL_LINKS
|
|
||||||
"<mask> <server> :<hopcount> <server info>"
|
|
||||||
365 RPL_ENDOFLINKS
|
|
||||||
"<mask> :End of /LINKS list"
|
|
||||||
|
|
||||||
- In replying to the LINKS message, a server must send
|
|
||||||
replies back using the RPL_LINKS numeric and mark the
|
|
||||||
end of the list using an RPL_ENDOFLINKS reply.
|
|
||||||
"
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc1459#page-51
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc2812#page-48
|
|
||||||
|
|
||||||
RPL_LINKS: "<client> * <server> :<hopcount> <server info>"
|
|
||||||
RPL_ENDOFLINKS: "<client> * :End of /LINKS list"
|
|
||||||
-- https://github.com/ircdocs/modern-irc/pull/175/files
|
|
||||||
"""
|
|
||||||
self.connectClient("nick")
|
|
||||||
self.sendLine(1, "LINKS")
|
|
||||||
messages = self.getMessages(1)
|
|
||||||
if messages[0].command == ERR_UNKNOWNCOMMAND:
|
|
||||||
raise runner.OptionalCommandNotSupported("LINKS")
|
|
||||||
|
|
||||||
# Ignore '/LINKS has been disabled' from ircu2
|
|
||||||
messages = [m for m in messages if m.command != "NOTICE"]
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
messages.pop(-1),
|
|
||||||
command=RPL_ENDOFLINKS,
|
|
||||||
params=["nick", "*", ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
if not messages:
|
|
||||||
# This server probably redacts links
|
|
||||||
return
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
messages[0],
|
|
||||||
command=RPL_LINKS,
|
|
||||||
params=[
|
|
||||||
"nick",
|
|
||||||
"My.Little.Server",
|
|
||||||
"My.Little.Server",
|
|
||||||
StrRe("0 (0042 )?test server"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@cases.mark_services
|
|
||||||
class ServicesLinksTestCase(cases.BaseServerTestCase):
|
|
||||||
# On every IRCd but Ergo, services are linked.
|
|
||||||
# Ergo does not implement LINKS at all, so this test is skipped.
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
|
||||||
def testLinksWithServices(self):
|
|
||||||
"""
|
|
||||||
Only testing the parameter-less case.
|
|
||||||
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc1459#section-4.3.3
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc2812#section-3.4.5
|
|
||||||
|
|
||||||
"
|
|
||||||
364 RPL_LINKS
|
|
||||||
"<mask> <server> :<hopcount> <server info>"
|
|
||||||
365 RPL_ENDOFLINKS
|
|
||||||
"<mask> :End of /LINKS list"
|
|
||||||
|
|
||||||
- In replying to the LINKS message, a server must send
|
|
||||||
replies back using the RPL_LINKS numeric and mark the
|
|
||||||
end of the list using an RPL_ENDOFLINKS reply.
|
|
||||||
"
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc1459#page-51
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc2812#page-48
|
|
||||||
|
|
||||||
RPL_LINKS: "<client> * <server> :<hopcount> <server info>"
|
|
||||||
RPL_ENDOFLINKS: "<client> * :End of /LINKS list"
|
|
||||||
-- https://github.com/ircdocs/modern-irc/pull/175/files
|
|
||||||
"""
|
|
||||||
self.connectClient("nick")
|
|
||||||
self.sendLine(1, "LINKS")
|
|
||||||
messages = self.getMessages(1)
|
|
||||||
|
|
||||||
if messages[0].command == ERR_UNKNOWNCOMMAND:
|
|
||||||
raise runner.OptionalCommandNotSupported("LINKS")
|
|
||||||
|
|
||||||
# Ignore '/LINKS has been disabled' from ircu2
|
|
||||||
messages = [m for m in messages if m.command != "NOTICE"]
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
messages.pop(-1),
|
|
||||||
command=RPL_ENDOFLINKS,
|
|
||||||
params=["nick", "*", ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
if not messages:
|
|
||||||
# This server redacts links
|
|
||||||
return
|
|
||||||
|
|
||||||
messages.sort(key=lambda m: m.params[-1])
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
messages.pop(0),
|
|
||||||
command=RPL_LINKS,
|
|
||||||
params=[
|
|
||||||
"nick",
|
|
||||||
"My.Little.Server",
|
|
||||||
"My.Little.Server",
|
|
||||||
StrRe("0 (0042 )?test server"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
self.assertMessageMatch(
|
|
||||||
messages.pop(0),
|
|
||||||
command=RPL_LINKS,
|
|
||||||
params=[
|
|
||||||
"nick",
|
|
||||||
"services.example.org",
|
|
||||||
"My.Little.Server",
|
|
||||||
StrRe("1 .+"), # SID instead of description for Anope...
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(messages, [])
|
|
@ -1,71 +1,40 @@
|
|||||||
"""
|
from irctest import cases
|
||||||
The LIST command (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.6>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.6>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#list-message>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from irctest import cases, runner
|
|
||||||
from irctest.numerics import RPL_LIST, RPL_LISTEND, RPL_LISTSTART
|
|
||||||
|
|
||||||
|
|
||||||
class _BasedListTestCase(cases.BaseServerTestCase):
|
class ListTestCase(cases.BaseServerTestCase):
|
||||||
def _parseChanList(self, client):
|
|
||||||
channels = set()
|
|
||||||
while True:
|
|
||||||
m = self.getMessage(client)
|
|
||||||
if m.command == RPL_LISTEND:
|
|
||||||
break
|
|
||||||
if m.command == RPL_LIST:
|
|
||||||
if m.params[1].startswith("&"):
|
|
||||||
# skip local pseudo-channels listed by ngircd and ircu
|
|
||||||
continue
|
|
||||||
channels.add(m.params[1])
|
|
||||||
|
|
||||||
return channels
|
|
||||||
|
|
||||||
|
|
||||||
class ListTestCase(_BasedListTestCase):
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
@cases.xfailIfSoftware(["irc2"], "irc2 deprecated LIST")
|
|
||||||
def testListEmpty(self):
|
def testListEmpty(self):
|
||||||
"""<https://tools.ietf.org/html/rfc1459#section-4.2.6>
|
"""<https://tools.ietf.org/html/rfc1459#section-4.2.6>
|
||||||
<https://tools.ietf.org/html/rfc2812#section-3.2.6>
|
<https://tools.ietf.org/html/rfc2812#section-3.2.6>
|
||||||
<https://modern.ircdocs.horse/#list-message>
|
|
||||||
"""
|
"""
|
||||||
self.connectClient("foo")
|
self.connectClient("foo")
|
||||||
self.connectClient("bar")
|
self.connectClient("bar")
|
||||||
self.getMessages(1)
|
self.getMessages(1)
|
||||||
self.sendLine(2, "LIST")
|
self.sendLine(2, "LIST")
|
||||||
m = self.getMessage(2)
|
m = self.getMessage(2)
|
||||||
if m.command == RPL_LISTSTART:
|
if m.command == "321":
|
||||||
# skip
|
# skip RPL_LISTSTART
|
||||||
m = self.getMessage(2)
|
m = self.getMessage(2)
|
||||||
# skip local pseudo-channels listed by ngircd and ircu
|
# skip local pseudo-channels listed by ngircd and ircu
|
||||||
while m.command == RPL_LIST and m.params[1].startswith("&"):
|
while m.command == "322" and m.params[1].startswith("&"):
|
||||||
m = self.getMessage(2)
|
m = self.getMessage(2)
|
||||||
self.assertNotEqual(
|
self.assertNotEqual(
|
||||||
m.command,
|
m.command,
|
||||||
RPL_LIST,
|
"322", # RPL_LIST
|
||||||
"LIST response gives (at least) one channel, whereas there " "is none.",
|
"LIST response gives (at least) one channel, whereas there " "is none.",
|
||||||
)
|
)
|
||||||
self.assertMessageMatch(
|
self.assertMessageMatch(
|
||||||
m,
|
m,
|
||||||
command=RPL_LISTEND,
|
command="323", # RPL_LISTEND
|
||||||
fail_msg="Second reply to LIST is not 322 (RPL_LIST) "
|
fail_msg="Second reply to LIST is not 322 (RPL_LIST) "
|
||||||
"or 323 (RPL_LISTEND), or but: {msg}",
|
"or 323 (RPL_LISTEND), or but: {msg}",
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
@cases.xfailIfSoftware(["irc2"], "irc2 deprecated LIST")
|
|
||||||
def testListOne(self):
|
def testListOne(self):
|
||||||
"""When a channel exists, LIST should get it in a reply.
|
"""When a channel exists, LIST should get it in a reply.
|
||||||
<https://tools.ietf.org/html/rfc1459#section-4.2.6>
|
<https://tools.ietf.org/html/rfc1459#section-4.2.6>
|
||||||
<https://tools.ietf.org/html/rfc2812#section-3.2.6>
|
<https://tools.ietf.org/html/rfc2812#section-3.2.6>
|
||||||
|
|
||||||
<https://modern.ircdocs.horse/#list-message>
|
|
||||||
"""
|
"""
|
||||||
self.connectClient("foo")
|
self.connectClient("foo")
|
||||||
self.connectClient("bar")
|
self.connectClient("bar")
|
||||||
@ -73,460 +42,34 @@ class ListTestCase(_BasedListTestCase):
|
|||||||
self.getMessages(1)
|
self.getMessages(1)
|
||||||
self.sendLine(2, "LIST")
|
self.sendLine(2, "LIST")
|
||||||
m = self.getMessage(2)
|
m = self.getMessage(2)
|
||||||
if m.command == RPL_LISTSTART:
|
if m.command == "321":
|
||||||
# skip
|
# skip RPL_LISTSTART
|
||||||
m = self.getMessage(2)
|
m = self.getMessage(2)
|
||||||
self.assertNotEqual(
|
self.assertNotEqual(
|
||||||
m.command,
|
m.command,
|
||||||
RPL_LISTEND,
|
"323", # RPL_LISTEND
|
||||||
fail_msg="LIST response ended (ie. 323, aka RPL_LISTEND) "
|
fail_msg="LIST response ended (ie. 323, aka RPL_LISTEND) "
|
||||||
"without listing any channel, whereas there is one.",
|
"without listing any channel, whereas there is one.",
|
||||||
)
|
)
|
||||||
self.assertMessageMatch(
|
self.assertMessageMatch(
|
||||||
m,
|
m,
|
||||||
command=RPL_LIST,
|
command="322", # RPL_LIST
|
||||||
fail_msg="Second reply to LIST is not 322 (RPL_LIST), "
|
fail_msg="Second reply to LIST is not 322 (RPL_LIST), "
|
||||||
"nor 323 (RPL_LISTEND) but: {msg}",
|
"nor 323 (RPL_LISTEND) but: {msg}",
|
||||||
)
|
)
|
||||||
m = self.getMessage(2)
|
m = self.getMessage(2)
|
||||||
# skip local pseudo-channels listed by ngircd and ircu
|
# skip local pseudo-channels listed by ngircd and ircu
|
||||||
while m.command == RPL_LIST and m.params[1].startswith("&"):
|
while m.command == "322" and m.params[1].startswith("&"):
|
||||||
m = self.getMessage(2)
|
m = self.getMessage(2)
|
||||||
self.assertNotEqual(
|
self.assertNotEqual(
|
||||||
m.command,
|
m.command,
|
||||||
RPL_LIST,
|
"322", # RPL_LIST
|
||||||
fail_msg="LIST response gives (at least) two channels, "
|
fail_msg="LIST response gives (at least) two channels, "
|
||||||
"whereas there is only one.",
|
"whereas there is only one.",
|
||||||
)
|
)
|
||||||
self.assertMessageMatch(
|
self.assertMessageMatch(
|
||||||
m,
|
m,
|
||||||
command=RPL_LISTEND,
|
command="323", # RPL_LISTEND
|
||||||
fail_msg="Third reply to LIST is not 322 (RPL_LIST) "
|
fail_msg="Third reply to LIST is not 322 (RPL_LIST) "
|
||||||
"or 323 (RPL_LISTEND), or but: {msg}",
|
"or 323 (RPL_LISTEND), or but: {msg}",
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812")
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Charybdis", "Solanum"],
|
|
||||||
"Charybdis and Solanum insert ERR_NOSUCHNICK reply in LIST",
|
|
||||||
)
|
|
||||||
def testListNonexistent(self):
|
|
||||||
"""LIST on a nonexistent channel does not send an error
|
|
||||||
response.
|
|
||||||
<https://tools.ietf.org/html/rfc1459#section-4.2.6>
|
|
||||||
<https://tools.ietf.org/html/rfc2812#section-3.2.6>
|
|
||||||
"""
|
|
||||||
self.connectClient("bar")
|
|
||||||
self.sendLine(1, "LIST #nonexistent")
|
|
||||||
responses = {msg.command for msg in self.getMessages(1)}
|
|
||||||
# successful response MUST include RPL_LISTEND:
|
|
||||||
self.assertIn(RPL_LISTEND, responses)
|
|
||||||
# and MUST NOT include RPL_LIST (since there is no matching channel)
|
|
||||||
# or any error numerics:
|
|
||||||
self.assertLessEqual(responses, {RPL_LISTSTART, RPL_LISTEND})
|
|
||||||
|
|
||||||
@cases.mark_isupport("ELIST")
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
def testListMask(self):
|
|
||||||
"""
|
|
||||||
"M: Searching based on mask."
|
|
||||||
-- <https://modern.ircdocs.horse/#elist-parameter>
|
|
||||||
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
|
|
||||||
"""
|
|
||||||
self.connectClient("foo")
|
|
||||||
|
|
||||||
if "M" not in self.server_support.get("ELIST", ""):
|
|
||||||
raise runner.OptionalExtensionNotSupported("ELIST=M")
|
|
||||||
|
|
||||||
self.connectClient("bar")
|
|
||||||
self.sendLine(1, "JOIN #chan1")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.sendLine(1, "JOIN #chan2")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST *an1")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST *an2")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST #c*n2")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST *an3")
|
|
||||||
self.assertEqual(self._parseChanList(2), set())
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST #ch*")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
|
|
||||||
|
|
||||||
@cases.mark_isupport("ELIST")
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
def testListNotMask(self):
|
|
||||||
"""
|
|
||||||
" N: Searching based on a non-matching mask. i.e., the opposite of M."
|
|
||||||
-- <https://modern.ircdocs.horse/#elist-parameter>
|
|
||||||
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
|
|
||||||
"""
|
|
||||||
self.connectClient("foo")
|
|
||||||
|
|
||||||
if "N" not in self.server_support.get("ELIST", ""):
|
|
||||||
raise runner.OptionalExtensionNotSupported("ELIST=N")
|
|
||||||
|
|
||||||
self.sendLine(1, "JOIN #chan1")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.sendLine(1, "JOIN #chan2")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.connectClient("bar")
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST !*an1")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST !*an2")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST !#c*n2")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST !*an3")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST !#ch*")
|
|
||||||
self.assertEqual(self._parseChanList(2), set())
|
|
||||||
|
|
||||||
@cases.mark_isupport("ELIST")
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
def testListUsers(self):
|
|
||||||
"""
|
|
||||||
"U: Searching based on user count within the channel, via the "<val" and
|
|
||||||
">val" modifiers to search for a channel that has less or more than val users,
|
|
||||||
respectively."
|
|
||||||
-- <https://modern.ircdocs.horse/#elist-parameter>
|
|
||||||
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
|
|
||||||
"""
|
|
||||||
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 >0")
|
|
||||||
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(3, "LIST <1")
|
|
||||||
self.assertEqual(self._parseChanList(3), set())
|
|
||||||
|
|
||||||
self.sendLine(3, "LIST <100")
|
|
||||||
self.assertEqual(self._parseChanList(3), {"#chan1", "#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(3, "LIST >1")
|
|
||||||
self.assertEqual(self._parseChanList(3), {"#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(3, "LIST <2")
|
|
||||||
self.assertEqual(self._parseChanList(3), {"#chan1"})
|
|
||||||
|
|
||||||
self.sendLine(3, "LIST <100")
|
|
||||||
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):
|
|
||||||
faketime = "+1y x30" # for every wall clock second, 1 minute passed for the server
|
|
||||||
|
|
||||||
def _sleep_minutes(self, n):
|
|
||||||
for _ in range(n):
|
|
||||||
if self.controller.faketime_enabled:
|
|
||||||
# From the server's point of view, 1 min will pass
|
|
||||||
time.sleep(2)
|
|
||||||
else:
|
|
||||||
time.sleep(60)
|
|
||||||
|
|
||||||
# reply to pings
|
|
||||||
self.getMessages(1)
|
|
||||||
self.getMessages(2)
|
|
||||||
|
|
||||||
@cases.mark_isupport("ELIST")
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Plexus4", "Hybrid"],
|
|
||||||
"Hybrid and Plexus4 filter on ELIST=C with the opposite meaning",
|
|
||||||
)
|
|
||||||
@cases.xfailIf(
|
|
||||||
lambda self: bool(
|
|
||||||
self.controller.software_name == "UnrealIRCd"
|
|
||||||
and self.controller.software_version == 5
|
|
||||||
),
|
|
||||||
"UnrealIRCd <6.0.3 filters on ELIST=C with the opposite meaning",
|
|
||||||
)
|
|
||||||
def testListCreationTime(self):
|
|
||||||
"""
|
|
||||||
" C: Searching based on channel creation time, via the "C<val" and "C>val"
|
|
||||||
modifiers to search for a channel creation time that is higher or lower
|
|
||||||
than val."
|
|
||||||
-- <https://modern.ircdocs.horse/#elist-parameter>
|
|
||||||
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
|
|
||||||
|
|
||||||
Unfortunately, this is ambiguous, because "val" is a time delta (in minutes),
|
|
||||||
not a timestamp.
|
|
||||||
|
|
||||||
On InspIRCd and Charybdis/Solanum, "C<val" is interpreted as "the channel was
|
|
||||||
created less than <val> minutes ago
|
|
||||||
|
|
||||||
On UnrealIRCd, Plexus, and Hybrid, it is interpreted as "the channel's creation
|
|
||||||
time is a timestamp lower than <val> minutes ago" (ie. the exact opposite)
|
|
||||||
|
|
||||||
"C: Searching based on channel creation time, via the "C<val" and "C>val"
|
|
||||||
modifiers to search for a channel that was created either less than `val`
|
|
||||||
minutes ago, or more than `val` minutes ago, respectively"
|
|
||||||
-- https://github.com/ircdocs/modern-irc/pull/171
|
|
||||||
"""
|
|
||||||
self.connectClient("foo")
|
|
||||||
|
|
||||||
if "C" not in self.server_support.get("ELIST", ""):
|
|
||||||
raise runner.OptionalExtensionNotSupported("ELIST=C")
|
|
||||||
|
|
||||||
self.connectClient("bar")
|
|
||||||
self.sendLine(1, "JOIN #chan1")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
# Helps debugging
|
|
||||||
self.sendLine(1, "TIME")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self._sleep_minutes(2)
|
|
||||||
|
|
||||||
# Helps debugging
|
|
||||||
self.sendLine(1, "TIME")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(1, "JOIN #chan2")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self._sleep_minutes(1)
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST C>2")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST C<2")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST C<0")
|
|
||||||
if self.controller.software_name == "InspIRCd":
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
|
|
||||||
else:
|
|
||||||
self.assertEqual(self._parseChanList(2), set())
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST C>0")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(2, "LIST C<10")
|
|
||||||
self.assertEqual(self._parseChanList(2), {"#chan1", "#chan2"})
|
|
||||||
|
|
||||||
@cases.mark_isupport("ELIST")
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
@cases.xfailIf(
|
|
||||||
lambda self: bool(
|
|
||||||
self.controller.software_name == "UnrealIRCd"
|
|
||||||
and self.controller.software_version == 5
|
|
||||||
),
|
|
||||||
"UnrealIRCd <6.0.3 advertises ELIST=T but does not implement it",
|
|
||||||
)
|
|
||||||
def testListTopicTime(self):
|
|
||||||
"""
|
|
||||||
"T: Searching based on topic time, via the "T<val" and "T>val"
|
|
||||||
modifiers to search for a topic time that is lower or higher than
|
|
||||||
val respectively."
|
|
||||||
-- <https://modern.ircdocs.horse/#elist-parameter>
|
|
||||||
-- https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.8
|
|
||||||
|
|
||||||
See testListCreationTime's docstring for comments on this.
|
|
||||||
|
|
||||||
"T: Searching based on topic set time, via the "T<val" and "T>val" modifiers
|
|
||||||
to search for a topic time that was set less than `val` minutes ago, or more
|
|
||||||
than `val` minutes ago, respectively."
|
|
||||||
-- https://github.com/ircdocs/modern-irc/pull/171
|
|
||||||
"""
|
|
||||||
self.connectClient("foo")
|
|
||||||
|
|
||||||
if "T" not in self.server_support.get("ELIST", ""):
|
|
||||||
raise runner.OptionalExtensionNotSupported("ELIST=T")
|
|
||||||
|
|
||||||
self.connectClient("bar")
|
|
||||||
self.sendLine(1, "JOIN #chan1")
|
|
||||||
self.sendLine(1, "JOIN #chan2")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(1, "TOPIC #chan1 :First channel")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
# Helps debugging
|
|
||||||
self.sendLine(1, "TIME")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self._sleep_minutes(2)
|
|
||||||
|
|
||||||
# Helps debugging
|
|
||||||
self.sendLine(1, "TIME")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(1, "TOPIC #chan2 :Second channel")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self._sleep_minutes(1)
|
|
||||||
|
|
||||||
self.sendLine(1, "LIST T>2")
|
|
||||||
self.assertEqual(self._parseChanList(1), {"#chan1"})
|
|
||||||
|
|
||||||
self.sendLine(1, "LIST T<2")
|
|
||||||
self.assertEqual(self._parseChanList(1), {"#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(1, "LIST T<0")
|
|
||||||
if self.controller.software_name == "InspIRCd":
|
|
||||||
# Insp internally represents "LIST T>0" like "LIST"
|
|
||||||
self.assertEqual(self._parseChanList(1), {"#chan1", "#chan2"})
|
|
||||||
else:
|
|
||||||
self.assertEqual(self._parseChanList(1), set())
|
|
||||||
|
|
||||||
self.sendLine(1, "LIST T>0")
|
|
||||||
self.assertEqual(self._parseChanList(1), {"#chan1", "#chan2"})
|
|
||||||
|
|
||||||
self.sendLine(1, "LIST T<10")
|
|
||||||
self.assertEqual(self._parseChanList(1), {"#chan1", "#chan2"})
|
|
||||||
|
@ -1,11 +1,3 @@
|
|||||||
"""
|
|
||||||
The LUSERS command (`RFC 2812
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc2812#section-3.4.2>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#lusers-message>`__),
|
|
||||||
which provides statistics on user counts.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import re
|
import re
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@ -153,10 +145,6 @@ class BasicLusersTestCase(LusersTestCase):
|
|||||||
self.getLusers("bar", True)
|
self.getLusers("bar", True)
|
||||||
|
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["ircu2", "Nefarious", "snircd"],
|
|
||||||
"test depends on Modern behavior, not just RFC2812",
|
|
||||||
)
|
|
||||||
def testLusersFull(self):
|
def testLusersFull(self):
|
||||||
self.connectClient("bar", name="bar")
|
self.connectClient("bar", name="bar")
|
||||||
lusers = self.getLusers("bar", False)
|
lusers = self.getLusers("bar", False)
|
||||||
@ -174,22 +162,10 @@ class BasicLusersTestCase(LusersTestCase):
|
|||||||
|
|
||||||
class LusersUnregisteredTestCase(LusersTestCase):
|
class LusersUnregisteredTestCase(LusersTestCase):
|
||||||
@cases.mark_specifications("RFC2812")
|
@cases.mark_specifications("RFC2812")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Nefarious"],
|
|
||||||
"Nefarious doesn't seem to distinguish unregistered users from normal ones",
|
|
||||||
)
|
|
||||||
def testLusersRfc2812(self):
|
def testLusersRfc2812(self):
|
||||||
self.doLusersTest(True)
|
self.doLusersTest(True)
|
||||||
|
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Nefarious"],
|
|
||||||
"Nefarious doesn't seem to distinguish unregistered users from normal ones",
|
|
||||||
)
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["ircu2", "Nefarious", "snircd"],
|
|
||||||
"test depends on Modern behavior, not just RFC2812",
|
|
||||||
)
|
|
||||||
def testLusersFull(self):
|
def testLusersFull(self):
|
||||||
self.doLusersTest(False)
|
self.doLusersTest(False)
|
||||||
|
|
||||||
@ -253,10 +229,6 @@ class LusersUnregisteredDefaultInvisibleTestCase(LusersUnregisteredTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("Ergo")
|
@cases.mark_specifications("Ergo")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Nefarious"],
|
|
||||||
"Nefarious doesn't seem to distinguish unregistered users from normal ones",
|
|
||||||
)
|
|
||||||
def testLusers(self):
|
def testLusers(self):
|
||||||
self.doLusersTest(False)
|
self.doLusersTest(False)
|
||||||
lusers = self.getLusers("bar", False)
|
lusers = self.getLusers("bar", False)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 message-tags <https://ircv3.net/specs/extensions/message-tags>`_
|
https://ircv3.net/specs/extensions/message-tags.html
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -10,7 +10,7 @@ from irctest.numerics import ERR_INPUTTOOLONG
|
|||||||
from irctest.patma import ANYDICT, ANYSTR, StrRe
|
from irctest.patma import ANYDICT, ANYSTR, StrRe
|
||||||
|
|
||||||
|
|
||||||
class MessageTagsTestCase(cases.BaseServerTestCase):
|
class MessageTagsTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@pytest.mark.arbitrary_client_tags
|
@pytest.mark.arbitrary_client_tags
|
||||||
@cases.mark_capabilities("message-tags")
|
@cases.mark_capabilities("message-tags")
|
||||||
def testBasic(self):
|
def testBasic(self):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
The PRIVMSG and NOTICE commands.
|
Section 3.2 of RFC 2812
|
||||||
|
<https://tools.ietf.org/html/rfc2812#section-3.3>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
@ -32,26 +33,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")
|
||||||
@ -71,15 +52,6 @@ class NoticeTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["InspIRCd"],
|
|
||||||
"replies with ERR_NOSUCHCHANNEL to NOTICE to non-existent channels",
|
|
||||||
)
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["UnrealIRCd"],
|
|
||||||
"replies with ERR_NOSUCHCHANNEL to NOTICE to non-existent channels: "
|
|
||||||
"https://bugs.unrealircd.org/view.php?id=5949",
|
|
||||||
)
|
|
||||||
def testNoticeNonexistentChannel(self):
|
def testNoticeNonexistentChannel(self):
|
||||||
"""
|
"""
|
||||||
"automatic replies must never be
|
"automatic replies must never be
|
||||||
@ -100,9 +72,6 @@ class NoticeTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
class TagsTestCase(cases.BaseServerTestCase):
|
class TagsTestCase(cases.BaseServerTestCase):
|
||||||
@cases.mark_capabilities("message-tags")
|
@cases.mark_capabilities("message-tags")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["UnrealIRCd"], "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)
|
||||||
self.connectClient(
|
self.connectClient(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
`Deprecated IRCv3 Metadata <https://ircv3.net/specs/core/metadata-3.2>`_
|
Tests METADATA features.
|
||||||
|
<http://ircv3.net/specs/core/metadata-3.2.html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 MONITOR <https://ircv3.net/specs/extensions/monitor>`_
|
<http://ircv3.net/specs/core/monitor-3.2.html>
|
||||||
and `IRCv3 extended-monitor` <https://ircv3.net/specs/extensions/extended-monitor>`_
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
from irctest import cases
|
||||||
|
from irctest.basecontrollers import NotImplementedByController
|
||||||
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 (
|
||||||
RPL_ENDOFMONLIST,
|
RPL_ENDOFMONLIST,
|
||||||
@ -16,10 +14,10 @@ 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 NotImplementedByController("MONITOR")
|
||||||
|
|
||||||
def assertMononline(self, client, nick, m=None):
|
def assertMononline(self, client, nick, m=None):
|
||||||
if not m:
|
if not m:
|
||||||
@ -45,8 +43,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 +296,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 +310,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")
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
`IRCv3 multi-prefix <https://ircv3.net/specs/extensions/multi-prefix>`_
|
Tests multi-prefix.
|
||||||
|
<http://ircv3.net/specs/extensions/multi-prefix-3.1.html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
`Draft IRCv3 multiline <https://ircv3.net/specs/extensions/multiline>`_
|
draft/multiline
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
@ -12,7 +12,7 @@ CONCAT_TAG = "draft/multiline-concat"
|
|||||||
base_caps = ["message-tags", "batch", "echo-message", "server-time", "labeled-response"]
|
base_caps = ["message-tags", "batch", "echo-message", "server-time", "labeled-response"]
|
||||||
|
|
||||||
|
|
||||||
class MultilineTestCase(cases.BaseServerTestCase):
|
class MultilineTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@cases.mark_capabilities("draft/multiline")
|
@cases.mark_capabilities("draft/multiline")
|
||||||
def testBasic(self):
|
def testBasic(self):
|
||||||
self.connectClient(
|
self.connectClient(
|
||||||
|
@ -1,114 +1,9 @@
|
|||||||
"""
|
from irctest import cases
|
||||||
The NAMES command (`RFC 1459
|
from irctest.numerics import RPL_ENDOFNAMES
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5>`__,
|
from irctest.patma import ANYSTR
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#names-message>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases, runner
|
|
||||||
from irctest.numerics import RPL_ENDOFNAMES, RPL_NAMREPLY
|
|
||||||
from irctest.patma import ANYSTR, StrRe
|
|
||||||
|
|
||||||
|
|
||||||
class NamesTestCase(cases.BaseServerTestCase):
|
class NamesTestCase(cases.BaseServerTestCase):
|
||||||
def _testNames(self, symbol):
|
|
||||||
self.connectClient("nick1")
|
|
||||||
self.sendLine(1, "JOIN #chan")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.connectClient("nick2")
|
|
||||||
self.sendLine(2, "JOIN #chan")
|
|
||||||
self.getMessages(2)
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(1, "NAMES #chan")
|
|
||||||
|
|
||||||
# TODO: It is technically allowed to have one line for each;
|
|
||||||
# but noone does that.
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_NAMREPLY,
|
|
||||||
params=[
|
|
||||||
"nick1",
|
|
||||||
*(["="] if symbol else []),
|
|
||||||
"#chan",
|
|
||||||
StrRe("(nick2 @nick1|@nick1 nick2)"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_ENDOFNAMES,
|
|
||||||
params=["nick1", "#chan", ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", deprecated=True)
|
|
||||||
def testNames1459(self):
|
|
||||||
"""
|
|
||||||
https://modern.ircdocs.horse/#names-message
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5
|
|
||||||
"""
|
|
||||||
self._testNames(symbol=False)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
|
||||||
def testNames2812(self):
|
|
||||||
"""
|
|
||||||
https://modern.ircdocs.horse/#names-message
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5
|
|
||||||
"""
|
|
||||||
self._testNames(symbol=True)
|
|
||||||
|
|
||||||
def _testNamesMultipleChannels(self, symbol):
|
|
||||||
self.connectClient("nick1")
|
|
||||||
|
|
||||||
if self.targmax.get("NAMES", "1") == "1":
|
|
||||||
raise runner.OptionalExtensionNotSupported("Multi-target NAMES")
|
|
||||||
|
|
||||||
self.sendLine(1, "JOIN #chan1")
|
|
||||||
self.sendLine(1, "JOIN #chan2")
|
|
||||||
self.getMessages(1)
|
|
||||||
|
|
||||||
self.sendLine(1, "NAMES #chan1,#chan2")
|
|
||||||
|
|
||||||
# TODO: order is unspecified
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_NAMREPLY,
|
|
||||||
params=["nick1", *(["="] if symbol else []), "#chan1", "@nick1"],
|
|
||||||
)
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_NAMREPLY,
|
|
||||||
params=["nick1", *(["="] if symbol else []), "#chan2", "@nick1"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_ENDOFNAMES,
|
|
||||||
params=["nick1", "#chan1,#chan2", ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_isupport("TARGMAX")
|
|
||||||
@cases.mark_specifications("RFC1459", deprecated=True)
|
|
||||||
def testNamesMultipleChannels1459(self):
|
|
||||||
"""
|
|
||||||
https://modern.ircdocs.horse/#names-message
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5
|
|
||||||
"""
|
|
||||||
self._testNamesMultipleChannels(symbol=False)
|
|
||||||
|
|
||||||
@cases.mark_isupport("TARGMAX")
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
|
||||||
def testNamesMultipleChannels2812(self):
|
|
||||||
"""
|
|
||||||
https://modern.ircdocs.horse/#names-message
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5
|
|
||||||
https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5
|
|
||||||
"""
|
|
||||||
self._testNamesMultipleChannels(symbol=True)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
||||||
def testNamesInvalidChannel(self):
|
def testNamesInvalidChannel(self):
|
||||||
"""
|
"""
|
||||||
@ -152,101 +47,3 @@ class NamesTestCase(cases.BaseServerTestCase):
|
|||||||
command=RPL_ENDOFNAMES,
|
command=RPL_ENDOFNAMES,
|
||||||
params=["foo", "#nonexisting", ANYSTR],
|
params=["foo", "#nonexisting", ANYSTR],
|
||||||
)
|
)
|
||||||
|
|
||||||
def _testNamesNoArgumentPublic(self, symbol):
|
|
||||||
self.connectClient("nick1")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.sendLine(1, "JOIN #chan1")
|
|
||||||
self.connectClient("nick2")
|
|
||||||
self.sendLine(2, "JOIN #chan2")
|
|
||||||
self.sendLine(2, "MODE #chan2 -sp")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.getMessages(2)
|
|
||||||
|
|
||||||
self.sendLine(1, "NAMES")
|
|
||||||
|
|
||||||
# TODO: order is unspecified
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_NAMREPLY,
|
|
||||||
params=["nick1", *(["="] if symbol else []), "#chan1", "@nick1"],
|
|
||||||
)
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_NAMREPLY,
|
|
||||||
params=["nick1", *(["="] if symbol else []), "#chan2", "@nick2"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_ENDOFNAMES,
|
|
||||||
params=["nick1", ANYSTR, ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", deprecated=True)
|
|
||||||
def testNamesNoArgumentPublic1459(self):
|
|
||||||
"""
|
|
||||||
"If no <channel> parameter is given, a list of all channels and their
|
|
||||||
occupants is returned."
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5
|
|
||||||
"""
|
|
||||||
self._testNamesNoArgumentPublic(symbol=False)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC2812", deprecated=True)
|
|
||||||
def testNamesNoArgumentPublic2812(self):
|
|
||||||
"""
|
|
||||||
"If no <channel> parameter is given, a list of all channels and their
|
|
||||||
occupants is returned."
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5
|
|
||||||
"""
|
|
||||||
self._testNamesNoArgumentPublic(symbol=True)
|
|
||||||
|
|
||||||
def _testNamesNoArgumentPrivate(self, symbol):
|
|
||||||
self.connectClient("nick1")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.sendLine(1, "JOIN #chan1")
|
|
||||||
self.connectClient("nick2")
|
|
||||||
self.sendLine(2, "JOIN #chan2")
|
|
||||||
self.sendLine(2, "MODE #chan2 +sp")
|
|
||||||
self.getMessages(1)
|
|
||||||
self.getMessages(2)
|
|
||||||
|
|
||||||
self.sendLine(1, "NAMES")
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_NAMREPLY,
|
|
||||||
params=["nick1", *(["="] if symbol else []), "#chan1", "@nick1"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage(1),
|
|
||||||
command=RPL_ENDOFNAMES,
|
|
||||||
params=["nick1", ANYSTR, ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", deprecated=True)
|
|
||||||
def testNamesNoArgumentPrivate1459(self):
|
|
||||||
"""
|
|
||||||
"If no <channel> parameter is given, a list of all channels and their
|
|
||||||
occupants is returned. At the end of this list, a list of users who
|
|
||||||
are visible but either not on any channel or not on a visible channel
|
|
||||||
are listed as being on `channel' "*"."
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5
|
|
||||||
"""
|
|
||||||
self._testNamesNoArgumentPrivate(symbol=False)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC2812", deprecated=True)
|
|
||||||
def testNamesNoArgumentPrivate2812(self):
|
|
||||||
"""
|
|
||||||
"If no <channel> parameter is given, a list of all channels and their
|
|
||||||
occupants is returned. At the end of this list, a list of users who
|
|
||||||
are visible but either not on any channel or not on a visible channel
|
|
||||||
are listed as being on `channel' "*"."
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.5
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.5
|
|
||||||
"""
|
|
||||||
self._testNamesNoArgumentPrivate(symbol=True)
|
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
"""
|
|
||||||
The PART command (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-6.1>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-5.2>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#part-message>`__)
|
|
||||||
|
|
||||||
TODO: cross-reference Modern
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
The PING and PONG commands
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.numerics import ERR_NEEDMOREPARAMS, ERR_NOORIGIN
|
from irctest.numerics import ERR_NEEDMOREPARAMS, ERR_NOORIGIN
|
||||||
from irctest.patma import ANYSTR
|
from irctest.patma import ANYSTR
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
"""
|
|
||||||
The QUITcommand (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.1.6>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.1>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#quit-message>`__)
|
|
||||||
|
|
||||||
TODO: cross-reference RFC 1459 and Modern
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
@ -16,7 +7,6 @@ from irctest.patma import StrRe
|
|||||||
|
|
||||||
class ChannelQuitTestCase(cases.BaseServerTestCase):
|
class ChannelQuitTestCase(cases.BaseServerTestCase):
|
||||||
@cases.mark_specifications("RFC2812")
|
@cases.mark_specifications("RFC2812")
|
||||||
@cases.xfailIfSoftware(["ircu2", "Nefarious", "snircd"], "ircu2 does not echo QUIT")
|
|
||||||
def testQuit(self):
|
def testQuit(self):
|
||||||
"""“Once a user has joined a channel, he receives information about
|
"""“Once a user has joined a channel, he receives information about
|
||||||
all commands his server receives affecting the channel. This
|
all commands his server receives affecting the channel. This
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
"""
|
|
||||||
`Ergo <https://ergo.chat/>`_-specific tests of responses to DoS attacks
|
|
||||||
using long lines.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
|
||||||
|
|
||||||
class ReadqTestCase(cases.BaseServerTestCase):
|
class ReadqTestCase(cases.BaseServerTestCase):
|
||||||
|
"""Test responses to DoS attacks using long lines."""
|
||||||
|
|
||||||
@cases.mark_specifications("Ergo")
|
@cases.mark_specifications("Ergo")
|
||||||
@cases.mark_capabilities("message-tags")
|
@cases.mark_capabilities("message-tags")
|
||||||
def testReadqTags(self):
|
def testReadqTags(self):
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
"""
|
|
||||||
`Draft IRCv3 account-registration
|
|
||||||
<https://ircv3.net/specs/extensions/account-registration>`_
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.patma import ANYSTR
|
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 +28,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 +55,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 +105,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
|
@ -1,14 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
Regression tests for bugs in `Ergo <https://ergo.chat/>`_.
|
Regression tests for bugs in oragono.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from irctest import cases, runner
|
import time
|
||||||
from irctest.numerics import (
|
|
||||||
ERR_ERRONEUSNICKNAME,
|
from irctest import cases
|
||||||
ERR_NICKNAMEINUSE,
|
from irctest.numerics import ERR_ERRONEUSNICKNAME, ERR_NICKNAMEINUSE, RPL_WELCOME
|
||||||
RPL_HELLO,
|
|
||||||
RPL_WELCOME,
|
|
||||||
)
|
|
||||||
from irctest.patma import ANYDICT
|
from irctest.patma import ANYDICT
|
||||||
|
|
||||||
|
|
||||||
@ -60,12 +57,6 @@ class RegressionsTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
@cases.mark_capabilities("message-tags", "batch", "echo-message", "server-time")
|
@cases.mark_capabilities("message-tags", "batch", "echo-message", "server-time")
|
||||||
def testTagCap(self):
|
def testTagCap(self):
|
||||||
if self.controller.software_name == "UnrealIRCd":
|
|
||||||
raise runner.ImplementationChoice(
|
|
||||||
"Arbitrary +draft/reply values (TODO: adapt this test to use real "
|
|
||||||
"values so their pass Unreal's validation) "
|
|
||||||
"https://bugs.unrealircd.org/view.php?id=5948"
|
|
||||||
)
|
|
||||||
# regression test for oragono #754
|
# regression test for oragono #754
|
||||||
self.connectClient(
|
self.connectClient(
|
||||||
"alice",
|
"alice",
|
||||||
@ -108,13 +99,13 @@ class RegressionsTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459")
|
@cases.mark_specifications("RFC1459")
|
||||||
@cases.xfailIfSoftware(["ngIRCd"], "wat")
|
|
||||||
def testStarNick(self):
|
def testStarNick(self):
|
||||||
self.addClient(1)
|
self.addClient(1)
|
||||||
self.sendLine(1, "NICK *")
|
self.sendLine(1, "NICK *")
|
||||||
self.sendLine(1, "USER u s e r")
|
self.sendLine(1, "USER u s e r")
|
||||||
replies = {"NOTICE"}
|
replies = {"NOTICE"}
|
||||||
while replies <= {"NOTICE", RPL_HELLO}:
|
time.sleep(2) # give time to slow servers, like irc2 to reply
|
||||||
|
while replies == {"NOTICE"}:
|
||||||
replies = set(msg.command for msg in self.getMessages(1, synchronize=False))
|
replies = set(msg.command for msg in self.getMessages(1, synchronize=False))
|
||||||
self.assertIn(ERR_ERRONEUSNICKNAME, replies)
|
self.assertIn(ERR_ERRONEUSNICKNAME, replies)
|
||||||
self.assertNotIn(RPL_WELCOME, replies)
|
self.assertNotIn(RPL_WELCOME, replies)
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
RELAYMSG command of `Ergo <https://ergo.chat/>`_
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.irc_utils.junkdrawer import random_name
|
from irctest.irc_utils.junkdrawer import random_name
|
||||||
from irctest.patma import ANYSTR
|
from irctest.patma import ANYSTR
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
Roleplay features of `Ergo <https://ergo.chat/>`_
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
from irctest.irc_utils.junkdrawer import random_name
|
from irctest.irc_utils.junkdrawer import random_name
|
||||||
from irctest.numerics import ERR_CANNOTSENDRP
|
from irctest.numerics import ERR_CANNOTSENDRP
|
||||||
|
@ -12,9 +12,9 @@ class RegistrationTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
|
|
||||||
@cases.mark_services
|
@cases.mark_services
|
||||||
class SaslTestCase(cases.BaseServerTestCase):
|
class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPlain(self):
|
def testPlain(self):
|
||||||
"""PLAIN authentication with correct username/password."""
|
"""PLAIN authentication with correct username/password."""
|
||||||
self.controller.registerUser(self, "foo", "sesame")
|
self.controller.registerUser(self, "foo", "sesame")
|
||||||
@ -54,7 +54,7 @@ class SaslTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPlainNonAscii(self):
|
def testPlainNonAscii(self):
|
||||||
password = "é" * 100
|
password = "é" * 100
|
||||||
authstring = base64.b64encode(
|
authstring = base64.b64encode(
|
||||||
@ -82,7 +82,7 @@ class SaslTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPlainNoAuthzid(self):
|
def testPlainNoAuthzid(self):
|
||||||
"""“message = [authzid] UTF8NUL authcid UTF8NUL passwd
|
"""“message = [authzid] UTF8NUL authcid UTF8NUL passwd
|
||||||
|
|
||||||
@ -170,22 +170,7 @@ class SaslTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
@cases.xfailIf(
|
|
||||||
lambda self: (
|
|
||||||
self.controller.services_controller is not None
|
|
||||||
and self.controller.services_controller.software_name == "Anope"
|
|
||||||
),
|
|
||||||
"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.
|
||||||
@ -247,14 +232,7 @@ class SaslTestCase(cases.BaseServerTestCase):
|
|||||||
# message's length too big for it to be valid.
|
# message's length too big for it to be valid.
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
@cases.xfailIf(
|
|
||||||
lambda self: (
|
|
||||||
self.controller.services_controller is not None
|
|
||||||
and self.controller.services_controller.software_name == "Anope"
|
|
||||||
),
|
|
||||||
"Anope does not handle split AUTHENTICATE (reported on IRC)",
|
|
||||||
)
|
|
||||||
def testPlainLargeEquals400(self):
|
def testPlainLargeEquals400(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.
|
||||||
@ -299,7 +277,7 @@ class SaslTestCase(cases.BaseServerTestCase):
|
|||||||
# message's length too big for it to be valid.
|
# message's length too big for it to be valid.
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.skipUnlessHasMechanism("SCRAM-SHA-256")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("SCRAM-SHA-256")
|
||||||
def testScramSha256Success(self):
|
def testScramSha256Success(self):
|
||||||
self.controller.registerUser(self, "Scramtest", "sesame")
|
self.controller.registerUser(self, "Scramtest", "sesame")
|
||||||
|
|
||||||
@ -355,7 +333,7 @@ class SaslTestCase(cases.BaseServerTestCase):
|
|||||||
self.confirmSuccessfulAuth()
|
self.confirmSuccessfulAuth()
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.skipUnlessHasMechanism("SCRAM-SHA-256")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("SCRAM-SHA-256")
|
||||||
def testScramSha256Failure(self):
|
def testScramSha256Failure(self):
|
||||||
self.controller.registerUser(self, "Scramtest", "sesame")
|
self.controller.registerUser(self, "Scramtest", "sesame")
|
||||||
|
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
"""
|
|
||||||
STATUSMSG ISUPPORT token and related PRIVMSG (`Modern
|
|
||||||
<https://modern.ircdocs.horse/#statusmsg-parameter>`__)
|
|
||||||
|
|
||||||
TODO: cross-reference Modern
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases, runner
|
from irctest import cases, runner
|
||||||
from irctest.numerics import RPL_NAMREPLY
|
from irctest.numerics import RPL_NAMREPLY
|
||||||
|
|
||||||
@ -17,11 +10,6 @@ class StatusmsgTestCase(cases.BaseServerTestCase):
|
|||||||
self.assertEqual(self.server_support["STATUSMSG"], "~&@%+")
|
self.assertEqual(self.server_support["STATUSMSG"], "~&@%+")
|
||||||
|
|
||||||
@cases.mark_isupport("STATUSMSG")
|
@cases.mark_isupport("STATUSMSG")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["ircu2", "Nefarious", "snircd"],
|
|
||||||
"STATUSMSG is present in ISUPPORT, but it not actually supported as PRIVMSG "
|
|
||||||
"target (only for WALLCOPS/WALLCHOPS/...)",
|
|
||||||
)
|
|
||||||
def testStatusmsgFromOp(self):
|
def testStatusmsgFromOp(self):
|
||||||
"""Test that STATUSMSG are sent to the intended recipients,
|
"""Test that STATUSMSG are sent to the intended recipients,
|
||||||
with the intended prefixes."""
|
with the intended prefixes."""
|
||||||
@ -73,11 +61,6 @@ class StatusmsgTestCase(cases.BaseServerTestCase):
|
|||||||
self.assertEqual(len(unprivilegedMessages), 0)
|
self.assertEqual(len(unprivilegedMessages), 0)
|
||||||
|
|
||||||
@cases.mark_isupport("STATUSMSG")
|
@cases.mark_isupport("STATUSMSG")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["ircu2", "Nefarious", "snircd"],
|
|
||||||
"STATUSMSG is present in ISUPPORT, but it not actually supported as PRIVMSG "
|
|
||||||
"target (only for WALLCOPS/WALLCHOPS/...)",
|
|
||||||
)
|
|
||||||
def testStatusmsgFromRegular(self):
|
def testStatusmsgFromRegular(self):
|
||||||
"""Test that STATUSMSG are sent to the intended recipients,
|
"""Test that STATUSMSG are sent to the intended recipients,
|
||||||
with the intended prefixes."""
|
with the intended prefixes."""
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import math
|
|
||||||
import time
|
|
||||||
|
|
||||||
from irctest import cases
|
|
||||||
from irctest.numerics import RPL_TIME
|
|
||||||
from irctest.patma import ANYSTR, StrRe
|
|
||||||
|
|
||||||
|
|
||||||
class TimeTestCase(cases.BaseServerTestCase):
|
|
||||||
def testTime(self):
|
|
||||||
self.connectClient("user")
|
|
||||||
|
|
||||||
time_before = math.floor(time.time())
|
|
||||||
self.sendLine(1, "TIME")
|
|
||||||
|
|
||||||
msg = self.getMessage(1)
|
|
||||||
|
|
||||||
time_after = math.ceil(time.time())
|
|
||||||
|
|
||||||
if len(msg.params) == 5:
|
|
||||||
# ircu2, snircd
|
|
||||||
self.assertMessageMatch(
|
|
||||||
msg,
|
|
||||||
command=RPL_TIME,
|
|
||||||
params=["user", "My.Little.Server", StrRe("[0-9]+"), "0", ANYSTR],
|
|
||||||
)
|
|
||||||
self.assertIn(
|
|
||||||
int(msg.params[2]),
|
|
||||||
range(time_before, time_after + 1),
|
|
||||||
"Timestamp not in expected range",
|
|
||||||
)
|
|
||||||
elif len(msg.params) == 4:
|
|
||||||
# bahamut
|
|
||||||
self.assertMessageMatch(
|
|
||||||
msg,
|
|
||||||
command=RPL_TIME,
|
|
||||||
params=["user", "My.Little.Server", StrRe("[0-9]+"), ANYSTR],
|
|
||||||
)
|
|
||||||
self.assertIn(
|
|
||||||
int(msg.params[2]),
|
|
||||||
range(time_before, time_after + 1),
|
|
||||||
"Timestamp not in expected range",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Common case
|
|
||||||
self.assertMessageMatch(
|
|
||||||
msg, command=RPL_TIME, params=["user", "My.Little.Server", ANYSTR]
|
|
||||||
)
|
|
@ -1,10 +1,3 @@
|
|||||||
"""
|
|
||||||
The TOPIC command (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.1>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.1>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#topic-message>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases, client_mock, runner
|
from irctest import cases, client_mock, runner
|
||||||
from irctest.numerics import ERR_CHANOPRIVSNEEDED, RPL_NOTOPIC, RPL_TOPIC, RPL_TOPICTIME
|
from irctest.numerics import ERR_CHANOPRIVSNEEDED, RPL_NOTOPIC, RPL_TOPIC, RPL_TOPICTIME
|
||||||
|
|
||||||
|
@ -1,124 +0,0 @@
|
|||||||
"""
|
|
||||||
Test the registered-only DM user mode (commonly +R).
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases
|
|
||||||
from irctest.numerics import ERR_NEEDREGGEDNICK
|
|
||||||
|
|
||||||
|
|
||||||
@cases.mark_services
|
|
||||||
class RegisteredOnlyUmodeTestCase(cases.BaseServerTestCase):
|
|
||||||
@cases.mark_specifications("Ergo")
|
|
||||||
def testRegisteredOnlyUserMode(self):
|
|
||||||
"""Test the +R registered-only mode."""
|
|
||||||
self.controller.registerUser(self, "evan", "sesame")
|
|
||||||
self.controller.registerUser(self, "carmen", "pink")
|
|
||||||
|
|
||||||
self.connectClient(
|
|
||||||
"evan",
|
|
||||||
name="evan",
|
|
||||||
account="evan",
|
|
||||||
password="sesame",
|
|
||||||
capabilities=["sasl"],
|
|
||||||
)
|
|
||||||
self.connectClient("shivaram", name="shivaram")
|
|
||||||
self.sendLine("evan", "MODE evan +R")
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("evan"), command="MODE", params=["evan", "+R"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# this DM should be blocked by +R registered-only
|
|
||||||
self.getMessages("shivaram")
|
|
||||||
self.sendLine("shivaram", "PRIVMSG evan :hey there")
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("shivaram"),
|
|
||||||
command=ERR_NEEDREGGEDNICK,
|
|
||||||
)
|
|
||||||
self.assertEqual(self.getMessages("evan"), [])
|
|
||||||
|
|
||||||
self.connectClient(
|
|
||||||
"carmen",
|
|
||||||
name="carmen",
|
|
||||||
account="carmen",
|
|
||||||
password="pink",
|
|
||||||
capabilities=["sasl"],
|
|
||||||
)
|
|
||||||
self.getMessages("evan")
|
|
||||||
self.sendLine("carmen", "PRIVMSG evan :hey there")
|
|
||||||
self.assertEqual(self.getMessages("carmen"), [])
|
|
||||||
# this message should go through fine:
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("evan"),
|
|
||||||
command="PRIVMSG",
|
|
||||||
params=["evan", "hey there"],
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("Ergo")
|
|
||||||
def testRegisteredOnlyUserModeAcceptCommand(self):
|
|
||||||
"""Test that the ACCEPT command can authorize another user
|
|
||||||
to send the accept-er direct messages, overriding the
|
|
||||||
+R registered-only mode."""
|
|
||||||
self.controller.registerUser(self, "evan", "sesame")
|
|
||||||
self.connectClient(
|
|
||||||
"evan",
|
|
||||||
name="evan",
|
|
||||||
account="evan",
|
|
||||||
password="sesame",
|
|
||||||
capabilities=["sasl"],
|
|
||||||
)
|
|
||||||
self.connectClient("shivaram", name="shivaram")
|
|
||||||
self.sendLine("evan", "MODE evan +R")
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("evan"), command="MODE", params=["evan", "+R"]
|
|
||||||
)
|
|
||||||
self.sendLine("evan", "ACCEPT shivaram")
|
|
||||||
self.getMessages("evan")
|
|
||||||
|
|
||||||
self.sendLine("shivaram", "PRIVMSG evan :hey there")
|
|
||||||
self.assertEqual(self.getMessages("shivaram"), [])
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("evan"),
|
|
||||||
command="PRIVMSG",
|
|
||||||
params=["evan", "hey there"],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.sendLine("evan", "ACCEPT -shivaram")
|
|
||||||
self.getMessages("evan")
|
|
||||||
self.sendLine("shivaram", "PRIVMSG evan :how's it going")
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("shivaram"),
|
|
||||||
command=ERR_NEEDREGGEDNICK,
|
|
||||||
)
|
|
||||||
self.assertEqual(self.getMessages("evan"), [])
|
|
||||||
|
|
||||||
@cases.mark_specifications("Ergo")
|
|
||||||
def testRegisteredOnlyUserModeAutoAcceptOnDM(self):
|
|
||||||
"""Test that sending someone a DM automatically authorizes them to
|
|
||||||
reply, overriding the +R registered-only mode."""
|
|
||||||
self.controller.registerUser(self, "evan", "sesame")
|
|
||||||
self.connectClient(
|
|
||||||
"evan",
|
|
||||||
name="evan",
|
|
||||||
account="evan",
|
|
||||||
password="sesame",
|
|
||||||
capabilities=["sasl"],
|
|
||||||
)
|
|
||||||
self.connectClient("shivaram", name="shivaram")
|
|
||||||
self.sendLine("evan", "MODE evan +R")
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("evan"), command="MODE", params=["evan", "+R"]
|
|
||||||
)
|
|
||||||
self.sendLine("evan", "PRIVMSG shivaram :hey there")
|
|
||||||
self.getMessages("evan")
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("shivaram"),
|
|
||||||
command="PRIVMSG",
|
|
||||||
params=["shivaram", "hey there"],
|
|
||||||
)
|
|
||||||
self.sendLine("shivaram", "PRIVMSG evan :how's it going")
|
|
||||||
self.assertEqual(self.getMessages("shivaram"), [])
|
|
||||||
self.assertMessageMatch(
|
|
||||||
self.getMessage("evan"),
|
|
||||||
command="PRIVMSG",
|
|
||||||
params=["evan", "how's it going"],
|
|
||||||
)
|
|
@ -1,21 +1,29 @@
|
|||||||
"""
|
from irctest import cases
|
||||||
`Ergo <https://ergo.chat/>`_-specific tests of non-Unicode filtering
|
|
||||||
|
|
||||||
<https://ircv3.net/specs/extensions/utf8-only>`_
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases, runner
|
|
||||||
from irctest.patma import ANYSTR
|
from irctest.patma import ANYSTR
|
||||||
|
|
||||||
|
|
||||||
class Utf8TestCase(cases.BaseServerTestCase):
|
class Utf8TestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@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 +31,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])
|
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
"""
|
|
||||||
The WALLOPS command (`RFC 2812
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc2812#section-3.7>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#wallops-message>`__)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from irctest import cases, runner
|
from irctest import cases, runner
|
||||||
from irctest.numerics import ERR_NOPRIVILEGES, ERR_UNKNOWNCOMMAND, RPL_YOUREOPER
|
from irctest.numerics import ERR_NOPRIVILEGES, ERR_UNKNOWNCOMMAND, RPL_YOUREOPER
|
||||||
from irctest.patma import ANYSTR, StrRe
|
from irctest.patma import ANYSTR, StrRe
|
||||||
@ -44,7 +38,7 @@ class WallopsTestCase(cases.BaseServerTestCase):
|
|||||||
|
|
||||||
messages = self.getMessages(1)
|
messages = self.getMessages(1)
|
||||||
if ERR_UNKNOWNCOMMAND in (message.command for message in messages):
|
if ERR_UNKNOWNCOMMAND in (message.command for message in messages):
|
||||||
raise runner.OptionalCommandNotSupported("WALLOPS")
|
raise runner.NotImplementedByController("WALLOPS")
|
||||||
for message in messages:
|
for message in messages:
|
||||||
self.assertMessageMatch(
|
self.assertMessageMatch(
|
||||||
message,
|
message,
|
||||||
@ -66,9 +60,6 @@ class WallopsTestCase(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["irc2"], "irc2 ignores the command instead of replying ERR_UNKNOWNCOMMAND"
|
|
||||||
)
|
|
||||||
def testWallopsPrivileges(self):
|
def testWallopsPrivileges(self):
|
||||||
"""
|
"""
|
||||||
https://github.com/ircdocs/modern-irc/pull/118
|
https://github.com/ircdocs/modern-irc/pull/118
|
||||||
@ -77,7 +68,7 @@ class WallopsTestCase(cases.BaseServerTestCase):
|
|||||||
self.sendLine(1, "WALLOPS :hi everyone")
|
self.sendLine(1, "WALLOPS :hi everyone")
|
||||||
message = self.getMessage(1)
|
message = self.getMessage(1)
|
||||||
if message.command == ERR_UNKNOWNCOMMAND:
|
if message.command == ERR_UNKNOWNCOMMAND:
|
||||||
raise runner.OptionalCommandNotSupported("WALLOPS")
|
raise runner.NotImplementedByController("WALLOPS")
|
||||||
self.assertMessageMatch(
|
self.assertMessageMatch(
|
||||||
message, command=ERR_NOPRIVILEGES, params=["nick1", ANYSTR]
|
message, command=ERR_NOPRIVILEGES, params=["nick1", ANYSTR]
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
"""
|
|
||||||
The WHO command (`Modern <https://modern.ircdocs.horse/#who-message>`__)
|
|
||||||
and `IRCv3 WHOX <https://ircv3.net/specs/extensions/whox>`_
|
|
||||||
|
|
||||||
TODO: cross-reference RFC 1459 and RFC 2812
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -37,8 +30,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)
|
||||||
@ -84,12 +77,9 @@ class BaseWhoTestCase:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
def testWhoStar(self):
|
def testWhoStar(self):
|
||||||
if self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHO mask")
|
|
||||||
|
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
self.sendLine(2, "WHO *")
|
self.sendLine(2, "WHO *")
|
||||||
@ -118,9 +108,6 @@ class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
def testWhoNick(self, mask):
|
def testWhoNick(self, mask):
|
||||||
if "*" in mask and self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHO mask")
|
|
||||||
|
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
self.sendLine(2, f"WHO {mask}")
|
self.sendLine(2, f"WHO {mask}")
|
||||||
@ -148,9 +135,6 @@ class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
|||||||
ids=["username", "realname-mask", "hostname"],
|
ids=["username", "realname-mask", "hostname"],
|
||||||
)
|
)
|
||||||
def testWhoUsernameRealName(self, mask):
|
def testWhoUsernameRealName(self, mask):
|
||||||
if "*" in mask and self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHO mask")
|
|
||||||
|
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
self.sendLine(2, f"WHO :{mask}")
|
self.sendLine(2, f"WHO :{mask}")
|
||||||
@ -201,9 +185,6 @@ class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
def testWhoNickAway(self, mask):
|
def testWhoNickAway(self, mask):
|
||||||
if "*" in mask and self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHO mask")
|
|
||||||
|
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
self.sendLine(1, "AWAY :be right back")
|
self.sendLine(1, "AWAY :be right back")
|
||||||
@ -230,9 +211,6 @@ class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
def testWhoNickOper(self, mask):
|
def testWhoNickOper(self, mask):
|
||||||
if "*" in mask and self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHO mask")
|
|
||||||
|
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
self.sendLine(1, "OPER operuser operpassword")
|
self.sendLine(1, "OPER operuser operpassword")
|
||||||
@ -264,9 +242,6 @@ class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
def testWhoNickAwayAndOper(self, mask):
|
def testWhoNickAwayAndOper(self, mask):
|
||||||
if "*" in mask and self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHO mask")
|
|
||||||
|
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
self.sendLine(1, "OPER operuser operpassword")
|
self.sendLine(1, "OPER operuser operpassword")
|
||||||
@ -298,9 +273,6 @@ class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
|||||||
@pytest.mark.parametrize("mask", ["#chan", "#CHAN"], ids=["exact", "casefolded"])
|
@pytest.mark.parametrize("mask", ["#chan", "#CHAN"], ids=["exact", "casefolded"])
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
def testWhoChan(self, mask):
|
def testWhoChan(self, mask):
|
||||||
if "*" in mask and self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHO mask")
|
|
||||||
|
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
self.sendLine(1, "OPER operuser operpassword")
|
self.sendLine(1, "OPER operuser operpassword")
|
||||||
@ -443,7 +415,9 @@ class WhoTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
|||||||
|
|
||||||
|
|
||||||
@cases.mark_services
|
@cases.mark_services
|
||||||
class WhoServicesTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
class WhoServicesTestCase(
|
||||||
|
BaseWhoTestCase, cases.BaseServerTestCase, cases.OptionalityHelper
|
||||||
|
):
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.mark_isupport("WHOX")
|
@cases.mark_isupport("WHOX")
|
||||||
def testWhoxAccount(self):
|
def testWhoxAccount(self):
|
||||||
@ -503,34 +477,3 @@ class WhoServicesTestCase(BaseWhoTestCase, cases.BaseServerTestCase):
|
|||||||
command=RPL_ENDOFWHO,
|
command=RPL_ENDOFWHO,
|
||||||
params=["otherNick", InsensitiveStr("coolNick"), ANYSTR],
|
params=["otherNick", InsensitiveStr("coolNick"), ANYSTR],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WhoInvisibleTestCase(cases.BaseServerTestCase):
|
|
||||||
@cases.mark_specifications("Modern")
|
|
||||||
def testWhoInvisible(self):
|
|
||||||
if self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHO mask")
|
|
||||||
|
|
||||||
self.connectClient("evan", name="evan")
|
|
||||||
self.sendLine("evan", "MODE evan +i")
|
|
||||||
self.getMessages("evan")
|
|
||||||
|
|
||||||
self.connectClient("shivaram", name="shivaram")
|
|
||||||
self.getMessages("shivaram")
|
|
||||||
self.sendLine("shivaram", "WHO eva*")
|
|
||||||
reply_cmds = {msg.command for msg in self.getMessages("shivaram")}
|
|
||||||
self.assertEqual(reply_cmds, {RPL_ENDOFWHO})
|
|
||||||
|
|
||||||
# invisibility should not be respected for plain nicknames, only for masks:
|
|
||||||
self.sendLine("shivaram", "WHO evan")
|
|
||||||
replies = self.getMessages("shivaram")
|
|
||||||
reply_cmds = {msg.command for msg in replies}
|
|
||||||
self.assertEqual(reply_cmds, {RPL_WHOREPLY, RPL_ENDOFWHO})
|
|
||||||
|
|
||||||
# invisibility should not be respected if the users share a channel
|
|
||||||
self.joinChannel("evan", "#test")
|
|
||||||
self.joinChannel("shivaram", "#test")
|
|
||||||
self.sendLine("shivaram", "WHO eva*")
|
|
||||||
replies = self.getMessages("shivaram")
|
|
||||||
reply_cmds = {msg.command for msg in replies}
|
|
||||||
self.assertEqual(reply_cmds, {RPL_WHOREPLY, RPL_ENDOFWHO})
|
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
"""
|
|
||||||
The WHOIS command (`Modern <https://modern.ircdocs.horse/#whois-message>`__)
|
|
||||||
|
|
||||||
TODO: cross-reference RFC 1459 and RFC 2812
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
@ -29,9 +23,6 @@ from irctest.patma import ANYSTR, StrRe
|
|||||||
|
|
||||||
class _WhoisTestMixin(cases.BaseServerTestCase):
|
class _WhoisTestMixin(cases.BaseServerTestCase):
|
||||||
def _testWhoisNumerics(self, authenticate, away, oper):
|
def _testWhoisNumerics(self, authenticate, away, oper):
|
||||||
if oper and self.controller.software_name == "Charybdis":
|
|
||||||
pytest.xfail("charybdis uses RPL_WHOISSPECIAL instead of RPL_WHOISOPERATOR")
|
|
||||||
|
|
||||||
if authenticate:
|
if authenticate:
|
||||||
self.connectClient("nick1")
|
self.connectClient("nick1")
|
||||||
self.controller.registerUser(self, "val", "sesame")
|
self.controller.registerUser(self, "val", "sesame")
|
||||||
@ -71,10 +62,7 @@ class _WhoisTestMixin(cases.BaseServerTestCase):
|
|||||||
last_message,
|
last_message,
|
||||||
command=RPL_ENDOFWHOIS,
|
command=RPL_ENDOFWHOIS,
|
||||||
params=["nick1", "nick2", ANYSTR],
|
params=["nick1", "nick2", ANYSTR],
|
||||||
fail_msg=(
|
fail_msg=f"Last message was not RPL_ENDOFWHOIS ({RPL_ENDOFWHOIS})",
|
||||||
f"Expected RPL_ENDOFWHOIS ({RPL_ENDOFWHOIS}) as last message, "
|
|
||||||
f"got {{msg}}"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
unexpected_messages = []
|
unexpected_messages = []
|
||||||
@ -99,12 +87,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"
|
||||||
@ -176,7 +158,7 @@ class _WhoisTestMixin(cases.BaseServerTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WhoisTestCase(_WhoisTestMixin, cases.BaseServerTestCase):
|
class WhoisTestCase(_WhoisTestMixin, cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"server",
|
"server",
|
||||||
["", "My.Little.Server", "coolNick"],
|
["", "My.Little.Server", "coolNick"],
|
||||||
@ -222,9 +204,11 @@ class WhoisTestCase(_WhoisTestMixin, cases.BaseServerTestCase):
|
|||||||
|
|
||||||
|
|
||||||
@cases.mark_services
|
@cases.mark_services
|
||||||
class ServicesWhoisTestCase(_WhoisTestMixin, cases.BaseServerTestCase):
|
class ServicesWhoisTestCase(
|
||||||
|
_WhoisTestMixin, cases.BaseServerTestCase, cases.OptionalityHelper
|
||||||
|
):
|
||||||
@pytest.mark.parametrize("oper", [False, True], ids=["normal", "oper"])
|
@pytest.mark.parametrize("oper", [False, True], ids=["normal", "oper"])
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("Modern")
|
||||||
def testWhoisNumerics(self, oper):
|
def testWhoisNumerics(self, oper):
|
||||||
"""Tests all numerics are in the exhaustive list defined in the Modern spec,
|
"""Tests all numerics are in the exhaustive list defined in the Modern spec,
|
||||||
@ -307,7 +291,7 @@ class ServicesWhoisTestCase(_WhoisTestMixin, cases.BaseServerTestCase):
|
|||||||
"RPL_WHOISCHANNELS should be sent for a non-invisible nick",
|
"RPL_WHOISCHANNELS should be sent for a non-invisible nick",
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
@cases.mark_specifications("ircdocs")
|
@cases.mark_specifications("ircdocs")
|
||||||
def testWhoisAccount(self):
|
def testWhoisAccount(self):
|
||||||
"""Test numeric 330, RPL_WHOISACCOUNT.
|
"""Test numeric 330, RPL_WHOISACCOUNT.
|
||||||
|
@ -1,19 +1,8 @@
|
|||||||
"""
|
|
||||||
The WHOSWAS command (`RFC 1459
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3>`__,
|
|
||||||
`RFC 2812 <https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3>`__,
|
|
||||||
`Modern <https://modern.ircdocs.horse/#whowas-message>`__)
|
|
||||||
|
|
||||||
TODO: cross-reference Modern
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from irctest import cases, runner
|
from irctest import cases, runner
|
||||||
from irctest.exceptions import ConnectionClosed
|
from irctest.exceptions import ConnectionClosed
|
||||||
from irctest.numerics import (
|
from irctest.numerics import (
|
||||||
ERR_NEEDMOREPARAMS,
|
|
||||||
ERR_NONICKNAMEGIVEN,
|
ERR_NONICKNAMEGIVEN,
|
||||||
ERR_WASNOSUCHNICK,
|
ERR_WASNOSUCHNICK,
|
||||||
RPL_ENDOFWHOWAS,
|
RPL_ENDOFWHOWAS,
|
||||||
@ -89,43 +78,6 @@ class WhowasTestCase(cases.BaseServerTestCase):
|
|||||||
unexpected_messages, [], fail_msg="Unexpected numeric messages: {got}"
|
unexpected_messages, [], fail_msg="Unexpected numeric messages: {got}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
|
||||||
def testWhowasEnd(self):
|
|
||||||
"""
|
|
||||||
"At the end of all reply batches, there must be RPL_ENDOFWHOWAS"
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc1459#page-50
|
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc2812#page-45
|
|
||||||
|
|
||||||
"Servers MUST reply with either ERR_WASNOSUCHNICK or [...],
|
|
||||||
both followed with RPL_ENDOFWHOWAS"
|
|
||||||
-- https://modern.ircdocs.horse/#whowas-message
|
|
||||||
"""
|
|
||||||
self.connectClient("nick1")
|
|
||||||
|
|
||||||
self.connectClient("nick2")
|
|
||||||
self.sendLine(2, "QUIT :bye")
|
|
||||||
try:
|
|
||||||
self.getMessages(2)
|
|
||||||
except ConnectionClosed:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.sendLine(1, "WHOWAS nick2")
|
|
||||||
|
|
||||||
messages = []
|
|
||||||
for _ in range(10):
|
|
||||||
messages.extend(self.getMessages(1))
|
|
||||||
if RPL_ENDOFWHOWAS in (m.command for m in messages):
|
|
||||||
break
|
|
||||||
|
|
||||||
last_message = messages.pop()
|
|
||||||
|
|
||||||
self.assertMessageMatch(
|
|
||||||
last_message,
|
|
||||||
command=RPL_ENDOFWHOWAS,
|
|
||||||
params=["nick1", "nick2", ANYSTR],
|
|
||||||
fail_msg=f"Last message was not RPL_ENDOFWHOWAS ({RPL_ENDOFWHOWAS})",
|
|
||||||
)
|
|
||||||
|
|
||||||
def _testWhowasMultiple(self, second_result, whowas_command):
|
def _testWhowasMultiple(self, second_result, whowas_command):
|
||||||
"""
|
"""
|
||||||
"The history is searched backward, returning the most recent entry first."
|
"The history is searched backward, returning the most recent entry first."
|
||||||
@ -200,84 +152,50 @@ class WhowasTestCase(cases.BaseServerTestCase):
|
|||||||
fail_msg=f"Last message was not RPL_ENDOFWHOWAS ({RPL_ENDOFWHOWAS})",
|
fail_msg=f"Last message was not RPL_ENDOFWHOWAS ({RPL_ENDOFWHOWAS})",
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
@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."
|
||||||
-- 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://modern.ircdocs.horse/#whowas-message
|
|
||||||
"""
|
"""
|
||||||
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")
|
||||||
@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"
|
||||||
-- 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://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")
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
@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"
|
||||||
-- 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://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")
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
@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
|
||||||
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
|
||||||
|
|
||||||
"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")
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["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
|
||||||
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
|
||||||
|
|
||||||
"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")
|
||||||
|
|
||||||
@ -286,15 +204,11 @@ 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://modern.ircdocs.horse/#whowas-message
|
|
||||||
"""
|
"""
|
||||||
if self.controller.software_name == "Bahamut":
|
|
||||||
raise runner.OptionalExtensionNotSupported("WHOWAS mask")
|
|
||||||
|
|
||||||
self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS *ck2")
|
self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS *ck2")
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", deprecated=True)
|
@cases.mark_specifications("RFC1459", "RFC2812", deprecated=True)
|
||||||
def testWhowasNoParamRfc(self):
|
def testWhowasNoParam(self):
|
||||||
"""
|
"""
|
||||||
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
|
||||||
@ -325,46 +239,11 @@ class WhowasTestCase(cases.BaseServerTestCase):
|
|||||||
params=["nick1", "nick2", ANYSTR],
|
params=["nick1", "nick2", ANYSTR],
|
||||||
)
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("Modern")
|
@cases.mark_specifications("RFC1459", "RFC2812")
|
||||||
def testWhowasNoParamModern(self):
|
|
||||||
"""
|
|
||||||
"If the `<nick>` argument is missing, they SHOULD send a single reply, using
|
|
||||||
either ERR_NONICKNAMEGIVEN or ERR_NEEDMOREPARAMS"
|
|
||||||
-- https://modern.ircdocs.horse/#whowas-message
|
|
||||||
"""
|
|
||||||
# But no one seems to follow this. Most implementations use ERR_NEEDMOREPARAMS
|
|
||||||
# instead of ERR_NONICKNAMEGIVEN; and I couldn't find any that returns
|
|
||||||
# RPL_ENDOFWHOWAS either way.
|
|
||||||
self.connectClient("nick1")
|
|
||||||
|
|
||||||
self.sendLine(1, "WHOWAS")
|
|
||||||
|
|
||||||
m = self.getMessage(1)
|
|
||||||
if m.command == ERR_NONICKNAMEGIVEN:
|
|
||||||
self.assertMessageMatch(
|
|
||||||
m,
|
|
||||||
command=ERR_NONICKNAMEGIVEN,
|
|
||||||
params=["nick1", ANYSTR],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.assertMessageMatch(
|
|
||||||
m,
|
|
||||||
command=ERR_NEEDMOREPARAMS,
|
|
||||||
params=["nick1", "WHOWAS", ANYSTR],
|
|
||||||
)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
|
|
||||||
@cases.xfailIfSoftware(
|
|
||||||
["Charybdis"],
|
|
||||||
"fails because of a typo (solved in "
|
|
||||||
"https://github.com/solanum-ircd/solanum/commit/"
|
|
||||||
"08b7b6bd7e60a760ad47b58cbe8075b45d66166f)",
|
|
||||||
)
|
|
||||||
def testWhowasNoSuchNick(self):
|
def testWhowasNoSuchNick(self):
|
||||||
"""
|
"""
|
||||||
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://modern.ircdocs.horse/#whowas-message
|
|
||||||
|
|
||||||
and:
|
and:
|
||||||
|
|
||||||
@ -372,12 +251,6 @@ class WhowasTestCase(cases.BaseServerTestCase):
|
|||||||
(even if there was only one reply and it was an error)."
|
(even if there was only one reply and it was an error)."
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc1459#page-50
|
-- https://datatracker.ietf.org/doc/html/rfc1459#page-50
|
||||||
-- https://datatracker.ietf.org/doc/html/rfc2812#page-45
|
-- https://datatracker.ietf.org/doc/html/rfc2812#page-45
|
||||||
|
|
||||||
and:
|
|
||||||
|
|
||||||
"Servers MUST reply with either ERR_WASNOSUCHNICK or [...],
|
|
||||||
both followed with RPL_ENDOFWHOWAS"
|
|
||||||
-- https://modern.ircdocs.horse/#whowas-message
|
|
||||||
"""
|
"""
|
||||||
self.connectClient("nick1")
|
self.connectClient("nick1")
|
||||||
|
|
||||||
@ -402,15 +275,15 @@ class WhowasTestCase(cases.BaseServerTestCase):
|
|||||||
"""
|
"""
|
||||||
https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
|
https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3
|
||||||
"""
|
"""
|
||||||
if self.controller.software_name == "Bahamut":
|
|
||||||
pytest.xfail(
|
|
||||||
"Bahamut returns entries in query order instead of chronological order"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.connectClient("nick1")
|
self.connectClient("nick1")
|
||||||
|
|
||||||
if self.targmax.get("WHOWAS", "1") == "1":
|
targmax = dict(
|
||||||
raise runner.OptionalExtensionNotSupported("Multi-target WHOWAS")
|
item.split(":", 1)
|
||||||
|
for item in self.server_support.get("TARGMAX", "").split(",")
|
||||||
|
if item
|
||||||
|
)
|
||||||
|
if targmax.get("WHOWAS", "1") == "1":
|
||||||
|
raise runner.NotImplementedByController("Multi-target WHOWAS")
|
||||||
|
|
||||||
self.connectClient("nick2", ident="ident2")
|
self.connectClient("nick2", ident="ident2")
|
||||||
self.sendLine(2, "QUIT :bye")
|
self.sendLine(2, "QUIT :bye")
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
`Ergo <https://ergo.chat/>`_-specific tests of ZNC-like message playback
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from irctest import cases
|
from irctest import cases
|
||||||
|
@ -27,19 +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_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
|
||||||
@ -52,15 +49,11 @@ class Capabilities(enum.Enum):
|
|||||||
|
|
||||||
@enum.unique
|
@enum.unique
|
||||||
class IsupportTokens(enum.Enum):
|
class IsupportTokens(enum.Enum):
|
||||||
ACCOUNTEXTBAN = "ACCOUNTEXTBAN"
|
|
||||||
BOT = "BOT"
|
BOT = "BOT"
|
||||||
ELIST = "ELIST"
|
|
||||||
INVEX = "INVEX"
|
|
||||||
PREFIX = "PREFIX"
|
PREFIX = "PREFIX"
|
||||||
MONITOR = "MONITOR"
|
MONITOR = "MONITOR"
|
||||||
STATUSMSG = "STATUSMSG"
|
STATUSMSG = "STATUSMSG"
|
||||||
TARGMAX = "TARGMAX"
|
TARGMAX = "TARGMAX"
|
||||||
UTF8ONLY = "UTF8ONLY"
|
|
||||||
WHOX = "WHOX"
|
WHOX = "WHOX"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -10,6 +10,7 @@ and keep them in sync.
|
|||||||
|
|
||||||
import enum
|
import enum
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import textwrap
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ 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",
|
||||||
@ -144,9 +145,13 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
|
|||||||
downloads = []
|
downloads = []
|
||||||
install_steps = []
|
install_steps = []
|
||||||
for software_id in test_config.get("software", []):
|
for software_id in test_config.get("software", []):
|
||||||
software_config = config["software"][software_id]
|
if software_id == "anope":
|
||||||
|
# TODO: don't hardcode anope here
|
||||||
|
software_config = {"separate_build_job": True}
|
||||||
|
else:
|
||||||
|
software_config = config["software"][software_id]
|
||||||
|
|
||||||
env += software_config.get("env", "") + " "
|
env += test_config.get("env", {}).get(version_flavor.value, "") + " "
|
||||||
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 +196,7 @@ 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@v2"},
|
{"uses": "actions/checkout@v2"},
|
||||||
@ -204,8 +209,8 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
|
|||||||
*unpack,
|
*unpack,
|
||||||
*install_steps,
|
*install_steps,
|
||||||
{
|
{
|
||||||
"name": "Install system dependencies",
|
"name": "Install Atheme",
|
||||||
"run": "sudo apt-get install atheme-services faketime",
|
"run": "sudo apt-get install atheme-services",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Install irctest dependencies",
|
"name": "Install irctest dependencies",
|
||||||
@ -221,7 +226,6 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Test with pytest",
|
"name": "Test with pytest",
|
||||||
"timeout-minutes": 30,
|
|
||||||
"run": (
|
"run": (
|
||||||
f"PYTEST_ARGS='--junit-xml pytest.xml' "
|
f"PYTEST_ARGS='--junit-xml pytest.xml' "
|
||||||
f"PATH=$HOME/.local/bin:$PATH "
|
f"PATH=$HOME/.local/bin:$PATH "
|
||||||
@ -233,7 +237,7 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
|
|||||||
"if": "always()",
|
"if": "always()",
|
||||||
"uses": "actions/upload-artifact@v2",
|
"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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -241,6 +245,47 @@ def get_test_job(*, config, test_config, test_id, version_flavor, jobs):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_build_job_anope():
|
||||||
|
return {
|
||||||
|
"runs-on": "ubuntu-latest",
|
||||||
|
"steps": [
|
||||||
|
{"uses": "actions/checkout@v2"},
|
||||||
|
{
|
||||||
|
"name": "Create directories",
|
||||||
|
"run": "cd ~/; mkdir -p .local/ go/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cache Anope",
|
||||||
|
"uses": "actions/cache@v2",
|
||||||
|
"with": {
|
||||||
|
"path": "~/.cache\n${{ github.workspace }}/anope\n",
|
||||||
|
"key": "3-${{ runner.os }}-anope-2.0.9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Checkout Anope",
|
||||||
|
"uses": "actions/checkout@v2",
|
||||||
|
"with": {
|
||||||
|
"repository": "anope/anope",
|
||||||
|
"ref": "2.0.9",
|
||||||
|
"path": "anope",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Build Anope",
|
||||||
|
"run": script(
|
||||||
|
"cd $GITHUB_WORKSPACE/anope/",
|
||||||
|
"cp $GITHUB_WORKSPACE/data/anope/* .",
|
||||||
|
"CFLAGS=-O0 ./Config -quick",
|
||||||
|
"make -C build -j 4",
|
||||||
|
"make -C build install",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
*upload_steps("anope"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def upload_steps(software_id):
|
def upload_steps(software_id):
|
||||||
"""Make a tarball (to preserve permissions) and upload"""
|
"""Make a tarball (to preserve permissions) and upload"""
|
||||||
return [
|
return [
|
||||||
@ -263,6 +308,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}
|
||||||
@ -280,6 +326,7 @@ def generate_workflow(config: dict, version_flavor: VersionFlavor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
jobs = {}
|
jobs = {}
|
||||||
|
jobs["build-anope"] = get_build_job_anope()
|
||||||
|
|
||||||
for software_id in config["software"]:
|
for software_id in config["software"]:
|
||||||
software_config = config["software"][software_id]
|
software_config = config["software"][software_id]
|
||||||
@ -304,9 +351,9 @@ def generate_workflow(config: dict, version_flavor: VersionFlavor):
|
|||||||
jobs[f"test-{test_id}"] = test_job
|
jobs[f"test-{test_id}"] = test_job
|
||||||
|
|
||||||
jobs["publish-test-results"] = {
|
jobs["publish-test-results"] = {
|
||||||
"name": "Publish Dashboard",
|
"name": "Publish Unit Tests Results",
|
||||||
"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()",
|
||||||
@ -318,31 +365,32 @@ def generate_workflow(config: dict, version_flavor: VersionFlavor):
|
|||||||
"with": {"path": "artifacts"},
|
"with": {"path": "artifacts"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Install dashboard dependencies",
|
"name": "Publish Unit Test Results",
|
||||||
"run": script(
|
"uses": "actions/github-script@v4",
|
||||||
"python -m pip install --upgrade pip",
|
"if": "github.event_name == 'pull_request'",
|
||||||
"pip install defusedxml docutils -r requirements.txt",
|
"with": {
|
||||||
),
|
"result-encoding": "string",
|
||||||
},
|
"script": script(
|
||||||
{
|
textwrap.dedent(
|
||||||
"name": "Generate dashboard",
|
"""\
|
||||||
"run": script(
|
let body = '';
|
||||||
"shopt -s globstar",
|
const options = {};
|
||||||
"python3 -m irctest.dashboard.format dashboard/ artifacts/**/*.xml",
|
options.listeners = {
|
||||||
"echo '/ /index.xhtml' > dashboard/_redirects",
|
stdout: (data) => {
|
||||||
),
|
body += data.toString();
|
||||||
},
|
}
|
||||||
{
|
};
|
||||||
"name": "Install netlify-cli",
|
await exec.exec('bash', ['-c', 'shopt -s globstar; python3 report.py artifacts/**/*.xml'], options);
|
||||||
"run": "npm i -g netlify-cli",
|
github.issues.createComment({
|
||||||
},
|
issue_number: context.issue.number,
|
||||||
{
|
owner: context.repo.owner,
|
||||||
"name": "Deploy to Netlify",
|
repo: context.repo.repo,
|
||||||
"run": "./.github/deploy_to_netlify.py",
|
body: body,
|
||||||
"env": {
|
});
|
||||||
"NETLIFY_SITE_ID": "${{ secrets.NETLIFY_SITE_ID }}",
|
return body;
|
||||||
"NETLIFY_AUTH_TOKEN": "${{ secrets.NETLIFY_AUTH_TOKEN }}",
|
"""
|
||||||
"GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}",
|
)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
6
mypy.ini
6
mypy.ini
@ -12,12 +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.*]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
|
|
||||||
[mypy-ecdsa]
|
[mypy-ecdsa]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user