Skip to content

Commit

Permalink
Merge pull request geoserver#536 from vuilleumierc/issue-521-acceptan…
Browse files Browse the repository at this point in the history
…ce-tests

Add acceptance tests
  • Loading branch information
groldan authored Oct 1, 2024
2 parents 645e1b6 + 7b83563 commit 4299702
Show file tree
Hide file tree
Showing 41 changed files with 1,367 additions and 1 deletion.
28 changes: 28 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,34 @@ jobs:
run: |
make build-image
- name: Install CI dependencies
run: python3 -m pip install --user --requirement=ci/requirements.txt

- name: Run acceptance tests datadir
run: |
make acceptance-tests-datadir
- name: Print docker compose logs datadir
run: (cd compose && c2cciutils-docker-logs)
if: always()

- name: Cleanup acceptance tests datadir
run: |
make clean-acceptance-tests-datadir
# FIXME: fix pgconfig discrepancies before reactivating
# - name: Run acceptance tests pgconfig
# run: |
# make acceptance-tests-pgconfig

# - name: Print docker compose logs pgconfig
# run: (cd compose && c2cciutils-docker-logs)
# if: always()

# - name: Cleanup acceptance tests pgconfig
# run: |
# make clean-acceptance-tests-pgconfig

- name: Remove project jars from cached repository
run: |
rm -rf ~/.m2/repository/org/geoserver
Expand Down
33 changes: 32 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
all: install test build-image

TAG=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
TAG=$(shell mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
COSIGN_PASSWORD := $(COSIGN_PASSWORD)
COMPOSE_PGCONFIG_OPTIONS ?= -f compose.yml -f catalog-pgconfig.yml
COMPOSE_DATADIR_OPTIONS ?= -f compose.yml -f catalog-datadir.yml
COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS ?= $(COMPOSE_PGCONFIG_OPTIONS) -f acceptance.yml
COMPOSE_ACCEPTANCE_DATADIR_OPTIONS ?= $(COMPOSE_DATADIR_OPTIONS) -f acceptance.yml
UID=$(shell id -u)
GID=$(shell id -g)

clean:
./mvnw clean
Expand Down Expand Up @@ -78,3 +84,28 @@ verify-image:
fi; \
done'

.PHONY: build-acceptance
build-acceptance:
docker build --tag=acceptance:$(TAG) acceptance_tests

.PHONY: acceptance-tests-pgconfig
acceptance-tests-pgconfig:
acceptance-tests-pgconfig: build-acceptance
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) up -d)
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) exec -T acceptance bash -c 'until [ -f /tmp/healthcheck ]; do echo "Waiting for /tmp/healthcheck to be available..."; sleep 5; done && pytest . -vvv --color=yes')

.PHONY: clean-acceptance-tests-pgconfig
clean-acceptance-tests-pgconfig:
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) down -v)

.PHONY: acceptance-tests-datadir
acceptance-tests-datadir:
acceptance-tests-datadir: build-acceptance
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) up -d)
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) exec -T acceptance bash -c 'until [ -f /tmp/healthcheck ]; do echo "Waiting for /tmp/healthcheck to be available..."; sleep 5; done && pytest . -vvv --color=yes')

.PHONY: clean-acceptance-tests-datadir
clean-acceptance-tests-datadir:
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) down -v)
rm -rf compose/catalog-datadir/*
touch compose/catalog-datadir/.keep
3 changes: 3 additions & 0 deletions acceptance_tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ignore all python cache files recursively
**/__pycache__/
poetry.lock
22 changes: 22 additions & 0 deletions acceptance_tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM ubuntu:24.04 AS base

RUN apt-get update \
&& apt-get upgrade --assume-yes \
&& apt-get install --assume-yes --no-install-recommends \
vim curl jq libmagic1 zip python3-pip libpq-dev python3-dev gcc \
&& rm -rf /var/lib/apt/lists/* \
&& rm /usr/lib/python*/EXTERNALLY-MANAGED

COPY . /acceptance_tests

WORKDIR /acceptance_tests
RUN python3 -m pip install --disable-pip-version-check .

COPY entrypoint.py /bin/entrypoint.py

ENV PYTHONUNBUFFERED=1
ENTRYPOINT ["/bin/entrypoint.py"]

HEALTHCHECK --interval=5s --start-period=15s --retries=20 CMD test -f /tmp/healthcheck || exit 1

CMD ["sleep", "infinity"]
18 changes: 18 additions & 0 deletions acceptance_tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# GeoServer Cloud acceptance tests

## Requirements

[Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer)

## Installation

```shell
poetry install
```

# Run the tests
First start the docker composition then run:

```shell
GEOSERVER_URL=http://localhost:9090/geoserver/cloud poetry run pytest -vvv .
```
72 changes: 72 additions & 0 deletions acceptance_tests/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
import time
import os
import requests
import sys

# Set variables
start_time = time.time()

# Set a maximum timeout from the environment or use 60 seconds as default
max_time = int(os.getenv('MAX_TIMEOUT', 60))

# Set the GEOSERVER_URL from the environment or use the default value
GEOSERVER_URL = os.getenv('GEOSERVER_URL', 'http://gateway:8080/geoserver/cloud')
GEOSERVER_USERNAME = os.getenv('GEOSERVER_USERNAME', 'admin')
GEOSERVER_PASSWORD = os.getenv('GEOSERVER_PASSWORD', 'geoserver')

# is we want to start directly with the passed command
IGNORE_HEALTH_CHECK = os.getenv('IGNORE_HEALTH_CHECK', False)

# Timeout function
def timeout():
current_time = time.time()
if current_time - start_time > max_time:
return True
return False

# Array of endpoints to check
endpoints = [
f"{GEOSERVER_URL}/wms?SERVICE=WMS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/wfs?SERVICE=WFS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/wps?SERVICE=WPS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/wcs?SERVICE=WCS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/ows?SERVICE=WMS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/gwc",
f"{GEOSERVER_URL}/rest",
]

if not IGNORE_HEALTH_CHECK:
# Loop through each endpoint and check if it's available
for endpoint in endpoints:
print(f"Waiting for {endpoint} to be available...")
if timeout():
print("Timeout")
break

while True:
try:
# Make a request to the endpoint
response = requests.get(endpoint, auth=(GEOSERVER_USERNAME, GEOSERVER_PASSWORD))
if response.status_code == 200:
print(f"{endpoint} is up")
break
else:
print(f"{endpoint} returned status code {response.status_code}")
except requests.exceptions.RequestException as e:
print(f"{endpoint} is not available - retrying...")

if timeout():
print("Timeout reached")
break

time.sleep(1)

# create /tmp/healthcheck file to signal that the healthcheck is done
with open("/tmp/healthcheck", "w") as f:
f.write("done")
# Execute the command passed to the script anyway, this is useful for
# running the tests and see what breaks
if len(sys.argv) > 1:
command = sys.argv[1:]
os.execvp(command[0], command)
19 changes: 19 additions & 0 deletions acceptance_tests/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[tool.poetry]
name = "acceptance-tests"
version = "0.1.0"
description = "todo"
authors = ["todo"]
readme = "README.md"
packages = [{ include = "tests" }]

[tool.poetry.dependencies]
python = "^3.10"
pytest = "^8.3.3"
psycopg2-binary = "^2.9.9"
geoservercloud = "^0.2.5"
sqlalchemy = "^2.0.35"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
60 changes: 60 additions & 0 deletions acceptance_tests/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import os
from pathlib import Path

import pytest
import sqlalchemy
from geoservercloud import GeoServerCloud

GEOSERVER_URL = os.getenv("GEOSERVER_URL", "http://gateway:8080/geoserver/cloud")
RESOURCE_DIR = Path(__file__).parent / "resources"
# Database connection
PGHOST = "geodatabase"
PGPORT = 5432
PGDATABASE = "geodata"
PGUSER = "geodata"
PGPASSWORD = "geodata"
PGSCHEMA = "test1"
WORKSPACE = "test_workspace"
DATASTORE = "test_datastore"


@pytest.fixture(scope="session", autouse=True)
def engine():
yield sqlalchemy.create_engine(
f"postgresql://{PGUSER}:{PGPASSWORD}@{PGHOST}:{PGPORT}/{PGDATABASE}",
)


@pytest.fixture(scope="session", autouse=True)
def db_session(engine):
with engine.connect() as connection:
connection.execute(
sqlalchemy.sql.text(f"CREATE SCHEMA IF NOT EXISTS {PGSCHEMA}")
)
connection.execute(sqlalchemy.sql.text(f"SET SEARCH_PATH = {PGSCHEMA}"))
connection.commit()
yield connection
connection.execute(
sqlalchemy.sql.text(f"DROP SCHEMA IF EXISTS {PGSCHEMA} CASCADE")
)
connection.commit()


@pytest.fixture(scope="module")
def geoserver():
geoserver = GeoServerCloud(GEOSERVER_URL)
geoserver.recreate_workspace(WORKSPACE, set_default_workspace=True)
geoserver.create_pg_datastore(
workspace=WORKSPACE,
datastore=DATASTORE,
pg_host=PGHOST,
pg_port=PGPORT,
pg_db=PGDATABASE,
pg_user=PGUSER,
pg_password=PGPASSWORD,
pg_schema=PGSCHEMA,
set_default_datastore=True,
)
geoserver.publish_workspace(WORKSPACE)
yield geoserver
geoserver.delete_workspace(WORKSPACE)
15 changes: 15 additions & 0 deletions acceptance_tests/tests/lib/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pathlib import Path


def write_actual_image(response, tag):
file = Path(f"/tmp/{tag}_actual.png")
file.parent.mkdir(parents=True, exist_ok=True)
with open(file, "wb") as fs:
fs.write(response.read())


def compare_images(dir, tag):
actual = f"/tmp/{tag}_actual.png"
expected = f"{dir}/{tag}_expected.png"
with open(actual, "rb") as fs1, open(expected, "rb") as fs2:
assert fs1.read() == fs2.read()
Binary file added acceptance_tests/tests/resources/getmap_expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions acceptance_tests/tests/resources/localized_labels.sld
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
xmlns="http://www.opengis.net/sld"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:se="http://www.opengis.net/se"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamedLayer>
<Name>localized_labels</Name>
<UserStyle>
<Name>localized_labels</Name>
<FeatureTypeStyle>
<Rule>
<Name>Localized labels</Name>
<TextSymbolizer>
<Label>
<ogc:Function name="Recode">
<ogc:Function name="language"/>
<ogc:Literal/>
<ogc:PropertyName>label_default</ogc:PropertyName>
<ogc:Literal>de</ogc:Literal>
<ogc:PropertyName>label_de</ogc:PropertyName>
<ogc:Literal>fr</ogc:Literal>
<ogc:PropertyName>label_fr</ogc:PropertyName>
</ogc:Function>
</Label>
<Fill>
<CssParameter name="fill">#000000</CssParameter>
</Fill>
</TextSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
37 changes: 37 additions & 0 deletions acceptance_tests/tests/resources/localized_no_default.sld
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
xmlns="http://www.opengis.net/sld"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:se="http://www.opengis.net/se"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamedLayer>
<Name>localized_style</Name>
<UserStyle>
<Name>localized_no_default</Name>
<FeatureTypeStyle>
<Rule>
<Name>Localized, no default value</Name>
<Title>
<Localized lang="en">English</Localized>
<Localized lang="de">Deutsch</Localized>
<Localized lang="fr">Français</Localized>
<Localized lang="it">Italiano</Localized>
</Title>
<PointSymbolizer>
<Graphic>
<Mark>
<WellKnownName>circle</WellKnownName>
<Fill>
<CssParameter name="fill">#FF0000</CssParameter>
</Fill>
</Mark>
<Size>6</Size>
</Graphic>
</PointSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
Loading

0 comments on commit 4299702

Please sign in to comment.