Skip to content

Commit

Permalink
CI: minimal functional tests, unify build/test/lint workflow (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmetc authored Mar 14, 2023
1 parent 695ebbd commit 6658f29
Show file tree
Hide file tree
Showing 12 changed files with 849 additions and 64 deletions.
29 changes: 0 additions & 29 deletions .github/workflows/ci_golangci-lint.yml

This file was deleted.

35 changes: 0 additions & 35 deletions .github/workflows/go.yml

This file was deleted.

85 changes: 85 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Build + tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
strategy:
matrix:
go-version: ["1.20.1"]

name: "Build + tests"
runs-on: ubuntu-latest

steps:

- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}

- name: Check out code into the Go module directory
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Cache Go modules
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
%LocalAppData%\go-build
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go-version }}-go-
- name: Build
run: |
make build
- name: Run unit tests
run: |
go install github.com/kyoh86/richgo@v0.3.12
set -o pipefail
make test | richgo testfilter
env:
RICHGO_FORCE_COLOR: 1

- name: Cache virtualenvs
id: cache-pipenv
uses: actions/cache@v3
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}

- name: Install functional test dependencies
run: |
python3 -m pip install --upgrade pipenv wheel
pipenv install --deploy
docker network create net-test
- name: Run functional tests
env:
CROWDSEC_TEST_VERSION: dev
CROWDSEC_TEST_FLAVORS: full
CROWDSEC_TEST_NETWORK: net-test
CROWDSEC_TEST_TIMEOUT: 60
run: |
pipenv run pytest --durations=0 --color=yes
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.51
args: --issues-exit-code=1 --timeout 10m
only-new-issues: false
# the cache is already managed above, enabling it here
# gives errors when extracting
skip-pkg-cache: true
skip-build-cache: true
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ all: clean build
build: goversion clean
$(GOBUILD) $(LD_OPTS) -o $(BINARY_NAME)

.PHONY: test
test:
@$(GOTEST) ./...

clean:
@rm -f $(BINARY_NAME)
@rm -rf ${RELDIR}
Expand All @@ -55,4 +59,9 @@ release: build
@chmod +x $(RELDIR)/upgrade.sh
@tar cvzf crowdsec-blocklist-mirror-$(GOOS)-$(GOARCH).tgz $(RELDIR)

.PHONY: func-tests
func-tests: build
pipenv install --dev
pipenv run pytest -v

include mk/goversion.mk
10 changes: 10 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[packages]
pytest-dotenv = "*"
pytest-cs = {ref = "0.4.0", git = "https://github.com/crowdsecurity/pytest-cs.git"}

[dev-packages]
gnureadline = "*"
ipdb = "*"

[requires]
python_version = "3.10"
598 changes: 598 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions default.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CROWDSEC_TEST_VERSION="dev"
CROWDSEC_TEST_FLAVORS="full"
CROWDSEC_TEST_NETWORK="net-test"
6 changes: 6 additions & 0 deletions pytest-debug.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[pytest]
# drop to pdb on first failure
addopts = --pdb --pdbcls=IPython.terminal.debugger:Pdb
env_files =
.env
default.env
5 changes: 5 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[pytest]
addopts =
env_files =
.env
default.env
Empty file added test/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Full integration test with a real Crowdsec running in Docker
"""

import contextlib
import os
import pathlib
import secrets
import string

import pytest

SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
PROJECT_ROOT = SCRIPT_DIR.parent
bm_binary = PROJECT_ROOT.joinpath("crowdsec-blocklist-mirror")


# Create a lapi container, registers a bouncer
# and runs it with the updated config.
# - Returns context manager that yields a tuple of (bouncer, lapi)
@pytest.fixture(scope='session')
def bouncer_with_lapi(bouncer, crowdsec, bm_cfg_factory, api_key_factory, tmp_path_factory):
@contextlib.contextmanager
def closure(config_lapi=None, config_bouncer=None, api_key=None):
if config_bouncer is None:
config_bouncer = {}
if config_lapi is None:
config_lapi = {}
# can be overridden by config_lapi + config_bouncer
api_key = api_key_factory()
env = {
'BOUNCER_KEY_custom': api_key,
}
try:
env.update(config_lapi)
with crowdsec(environment=env) as lapi:
lapi.wait_for_http(8080, '/health')
port = lapi.probe.get_bound_port('8080')
cfg = bm_cfg_factory()
cfg.setdefault('crowdsec_config', {})
cfg['crowdsec_config']['lapi_url'] = f'http://localhost:{port}/'
cfg['crowdsec_config']['lapi_key'] = api_key
cfg.update(config_bouncer)
with bouncer(bm_binary, cfg) as cb:
yield cb, lapi
finally:
pass

yield closure


_default_config = {
'update_frequency': '0.1s',
'log_mode': 'stdout',
'log_level': 'info',
'prometheus': {
'enabled': False,
}
}


@pytest.fixture(scope='session')
def bm_cfg_factory():
def closure(**kw):
cfg = _default_config.copy()
cfg.setdefault('crowdsec_config', {})
cfg |= kw
return cfg | kw
yield closure


@pytest.fixture(scope='session')
def api_key_factory():
def closure(alphabet=string.ascii_letters + string.digits):
return ''.join(secrets.choice(alphabet) for i in range(32))
yield closure
57 changes: 57 additions & 0 deletions test/test_blocklist_mirror.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from .conftest import bm_binary


def test_no_api_key(crowdsec, bouncer, bm_cfg_factory):
cfg = bm_cfg_factory()
with bouncer(bm_binary, cfg) as bm:
bm.wait_for_lines_fnmatch([
"*lapi_key is not specified*",
])
bm.proc.wait(timeout=0.2)
assert not bm.proc.is_running()

cfg['crowdsec_config']['lapi_key'] = ''

with bouncer(bm_binary, cfg) as bm:
bm.wait_for_lines_fnmatch([
"*lapi_key is not specified*",
])
bm.proc.wait(timeout=0.2)
assert not bm.proc.is_running()


def test_no_lapi_url(bouncer, bm_cfg_factory):
cfg = bm_cfg_factory()

cfg['crowdsec_config']['lapi_key'] = 'not-used'

with bouncer(bm_binary, cfg) as bm:
bm.wait_for_lines_fnmatch([
"*lapi_url is not specified*",
])
bm.proc.wait(timeout=0.2)
assert not bm.proc.is_running()

cfg['crowdsec_config']['lapi_url'] = ''

with bouncer(bm_binary, cfg) as bm:
bm.wait_for_lines_fnmatch([
"*lapi_url is not specified*",
])
bm.proc.wait(timeout=0.2)
assert not bm.proc.is_running()


def test_no_lapi(bouncer, bm_cfg_factory):
cfg = bm_cfg_factory()
cfg['crowdsec_config']['lapi_key'] = 'not-used'
cfg['crowdsec_config']['lapi_url'] = 'http://localhost:8237'

with bouncer(bm_binary, cfg) as bm:
bm.wait_for_lines_fnmatch([
"*connection refused*",
# "*terminating bouncer process*",
"*can't access LAPI*",
])
bm.proc.wait(timeout=0.2)
assert not bm.proc.is_running()

0 comments on commit 6658f29

Please sign in to comment.