mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
server/tests-py: Add a --hge-bin
argument to the Pytest runner.
This argument allows the user to specify how to run HGE, rather than starting it beforehand. The runner will start a new instance of HGE for each test class. This does not provide isolation, as the database is still re-used, but it helps us get closer. You can try it yourself by executing: ``` $ cabal build graphql-engine:exe:graphql-engine $ ./server/tests-py/run-new.sh ``` This doesn't affect CI at all. I also fixed a few warnings flagged by Pylance. PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5881 GitOrigin-RevId: ea6f0fd631a2c278b2c6b50e9dbdd9d804ebc9d4
This commit is contained in:
parent
ab71adc3a0
commit
1a5aaae9cf
@ -7,11 +7,18 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from context import HGECtx, HGECtxError, HGECtxGQLServer, ActionsWebhookServer, EvtsWebhookServer, GQLWsClient, PytestConf, GraphQLWSClient
|
||||
from context import HGECtx, HGECtxGQLServer, ActionsWebhookServer, EvtsWebhookServer, GQLWsClient, PytestConf, GraphQLWSClient
|
||||
import fixtures.hge
|
||||
import graphql_server
|
||||
import ports
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--hge-bin",
|
||||
metavar="HGE_BIN",
|
||||
required=False,
|
||||
help="Hasura GraphQL Engine binary executable",
|
||||
)
|
||||
parser.addoption(
|
||||
"--hge-urls",
|
||||
metavar="HGE_URLS",
|
||||
@ -229,13 +236,13 @@ This option may result in test failures if the schema has to change between the
|
||||
|
||||
|
||||
#By default,
|
||||
#1) Set default parallelism to one
|
||||
#2) Set test grouping to by filename (--dist=loadfile)
|
||||
#1) Set test grouping to by class (--dist=loadfile)
|
||||
#2) Set default parallelism to one
|
||||
def pytest_cmdline_preparse(config, args):
|
||||
worker = os.environ.get('PYTEST_XDIST_WORKER')
|
||||
if 'xdist' in sys.modules and not worker: # pytest-xdist plugin
|
||||
num = 1
|
||||
args[:] = ["-n" + str(num),"--dist=loadfile"] + args
|
||||
args[:] = ['--dist=loadfile', f'-n{num}'] + args
|
||||
|
||||
def pytest_configure(config):
|
||||
# Pytest has removed the global pytest.config
|
||||
@ -244,15 +251,18 @@ def pytest_configure(config):
|
||||
if is_help_option_present(config):
|
||||
return
|
||||
if is_master(config):
|
||||
if not config.getoption('--hge-urls'):
|
||||
print("hge-urls should be specified")
|
||||
assert not config.getoption('--exitfirst'), 'The "--exitfirst"/"-x" option does not work with xdist.\nSee: https://github.com/pytest-dev/pytest-xdist/issues/54'
|
||||
if not (config.getoption('--hge-bin') or config.getoption('--hge-urls')):
|
||||
print("either --hge-bin or --hge-urls should be specified")
|
||||
if config.getoption('--hge-bin') and config.getoption('--hge-urls'):
|
||||
print("only one of --hge-bin or --hge-urls should be specified")
|
||||
if not config.getoption('--pg-urls'):
|
||||
print("pg-urls should be specified")
|
||||
config.hge_url_list = config.getoption('--hge-urls')
|
||||
config.pg_url_list = config.getoption('--pg-urls')
|
||||
if config.getoption('-n', default=None):
|
||||
xdist_threads = config.getoption('-n')
|
||||
assert xdist_threads <= len(config.hge_url_list), "Not enough hge_urls specified, Required " + str(xdist_threads) + ", got " + str(len(config.hge_url_list))
|
||||
assert config.getoption('--hge-bin') or xdist_threads <= len(config.hge_url_list), "Not enough hge_urls specified, Required " + str(xdist_threads) + ", got " + str(len(config.hge_url_list))
|
||||
assert xdist_threads <= len(config.pg_url_list), "Not enough pg_urls specified, Required " + str(xdist_threads) + ", got " + str(len(config.pg_url_list))
|
||||
|
||||
@pytest.hookimpl()
|
||||
@ -295,8 +305,8 @@ def pytest_report_collectionfinish(config, startdir, items):
|
||||
def pytest_configure_node(node):
|
||||
if is_help_option_present(node.config):
|
||||
return
|
||||
# Pytest has removed the global pytest.config
|
||||
node.workerinput["hge-url"] = node.config.hge_url_list.pop()
|
||||
if not node.config.getoption('--hge-bin'):
|
||||
node.workerinput["hge-url"] = node.config.hge_url_list.pop()
|
||||
node.workerinput["pg-url"] = node.config.pg_url_list.pop()
|
||||
|
||||
def run_on_current_backend(request: pytest.FixtureRequest):
|
||||
@ -324,40 +334,48 @@ def per_backend_test_function(request: pytest.FixtureRequest):
|
||||
return per_backend_tests_fixture(request)
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def postgis(hge_ctx):
|
||||
with sqlalchemy.create_engine(hge_ctx.pg_url).connect() as connection:
|
||||
def pg_url(request) -> str:
|
||||
return request.config.workerinput["pg-url"]
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def hge_url(request, hge_server) -> str:
|
||||
if hge_server:
|
||||
return hge_server
|
||||
else:
|
||||
return request.config.workerinput["hge-url"]
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def postgis(pg_url):
|
||||
with sqlalchemy.create_engine(pg_url).connect() as connection:
|
||||
connection.execute('CREATE EXTENSION IF NOT EXISTS postgis')
|
||||
connection.execute('CREATE EXTENSION IF NOT EXISTS postgis_topology')
|
||||
postgis_version = connection.execute('SELECT PostGIS_lib_version() as postgis_version').fetchone()['postgis_version']
|
||||
result = connection.execute('SELECT PostGIS_lib_version() as postgis_version').fetchone()
|
||||
if not result:
|
||||
raise Exception('Could not detect the PostGIS version.')
|
||||
postgis_version: str = result['postgis_version']
|
||||
if re.match('^3\\.', postgis_version):
|
||||
connection.execute('CREATE EXTENSION IF NOT EXISTS postgis_raster')
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def hge_ctx(request):
|
||||
config = request.config
|
||||
print("create hge_ctx")
|
||||
if is_master(config):
|
||||
hge_url = config.hge_url_list[0]
|
||||
else:
|
||||
hge_url = config.workerinput["hge-url"]
|
||||
def hge_port():
|
||||
return fixtures.hge.hge_port()
|
||||
|
||||
if is_master(config):
|
||||
pg_url = config.pg_url_list[0]
|
||||
else:
|
||||
pg_url = config.workerinput["pg-url"]
|
||||
@pytest.fixture(scope='class')
|
||||
def hge_server(
|
||||
request: pytest.FixtureRequest,
|
||||
hge_port: int,
|
||||
pg_url: str,
|
||||
):
|
||||
return fixtures.hge.hge_server(request, hge_port, pg_url)
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def hge_ctx(request, hge_url, pg_url):
|
||||
hge_ctx = HGECtx(hge_url, pg_url, request.config)
|
||||
|
||||
yield hge_ctx
|
||||
|
||||
try:
|
||||
hge_ctx = HGECtx(hge_url, pg_url, config)
|
||||
except HGECtxError as e:
|
||||
assert False, "Error from hge_ctx: " + str(e)
|
||||
# TODO this breaks things (https://github.com/pytest-dev/pytest-xdist/issues/86)
|
||||
# so at least make sure the real error gets printed (above)
|
||||
pytest.exit(str(e))
|
||||
yield hge_ctx # provide the fixture value
|
||||
print("teardown hge_ctx")
|
||||
hge_ctx.teardown()
|
||||
# TODO why do we sleep here?
|
||||
time.sleep(1)
|
||||
time.sleep(1) # TODO why do we sleep here?
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def evts_webhook(request):
|
||||
|
@ -58,7 +58,7 @@ services:
|
||||
|
||||
postgres:
|
||||
image: cimg/postgres:14.4-postgis@sha256:492a389895568e2f89a03c0c45c19350888611001123514623551a014e83a625
|
||||
expose:
|
||||
ports:
|
||||
- 5432
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "hasura"
|
||||
|
71
server/tests-py/fixtures/hge.py
Normal file
71
server/tests-py/fixtures/hge.py
Normal file
@ -0,0 +1,71 @@
|
||||
import os
|
||||
import pytest
|
||||
import subprocess
|
||||
import threading
|
||||
from typing import Optional
|
||||
|
||||
import ports
|
||||
|
||||
# These are the names of the environment variables that should be passed through to the HGE binary.
|
||||
# Other variables are ignored.
|
||||
_USED_ENV_VARS = set([
|
||||
'PATH', # required for basically anything to work
|
||||
'HASURA_GRAPHQL_PG_SOURCE_URL_1',
|
||||
'HASURA_GRAPHQL_PG_SOURCE_URL_2',
|
||||
'EVENT_WEBHOOK_HEADER',
|
||||
'EVENT_WEBHOOK_HANDLER',
|
||||
'ACTION_WEBHOOK_HANDLER',
|
||||
'SCHEDULED_TRIGGERS_WEBHOOK_DOMAIN',
|
||||
'REMOTE_SCHEMAS_WEBHOOK_DOMAIN',
|
||||
'GRAPHQL_SERVICE_HANDLER',
|
||||
'GRAPHQL_SERVICE_1',
|
||||
'GRAPHQL_SERVICE_2',
|
||||
'GRAPHQL_SERVICE_3',
|
||||
])
|
||||
|
||||
def hge_port() -> int:
|
||||
return ports.find_free_port()
|
||||
|
||||
def hge_server(
|
||||
request: pytest.FixtureRequest,
|
||||
hge_port: int,
|
||||
pg_url: str,
|
||||
) -> Optional[str]:
|
||||
hge_url = f'http://localhost:{hge_port}'
|
||||
hge_bin: str = request.config.getoption('--hge-bin') # type: ignore
|
||||
if not hge_bin:
|
||||
return None
|
||||
hge_env = {name: value for name, value in os.environ.items() if name in _USED_ENV_VARS}
|
||||
|
||||
print(f'Starting GraphQL Engine on {hge_url}...')
|
||||
hge_process = subprocess.Popen(
|
||||
args = [
|
||||
hge_bin,
|
||||
'--database-url', pg_url,
|
||||
'serve',
|
||||
'--server-port', str(hge_port),
|
||||
'--stringify-numeric-types',
|
||||
],
|
||||
env = hge_env,
|
||||
)
|
||||
|
||||
def stop():
|
||||
if hge_process.poll() is None:
|
||||
print(f'Stopping GraphQL Engine on {hge_url}...')
|
||||
hge_process.terminate()
|
||||
try:
|
||||
hge_process.wait(timeout = 5)
|
||||
print(f'GraphQL Engine on {hge_url} has stopped.')
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f'Given up waiting; killing GraphQL Engine...')
|
||||
hge_process.kill()
|
||||
hge_process.wait()
|
||||
print(f'GraphQL Engine has been successfully killed.')
|
||||
else:
|
||||
print(f'GraphQL Engine on {hge_url} has already stopped.')
|
||||
|
||||
# Stop in the background so we don't hold up other tests.
|
||||
request.addfinalizer(lambda: threading.Thread(target = stop).start())
|
||||
|
||||
ports.wait_for_port(hge_port, timeout = 30)
|
||||
return hge_url
|
46
server/tests-py/run-new.sh
Executable file
46
server/tests-py/run-new.sh
Executable file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This allows a developer to easily run the Python integration tests using the
|
||||
# `--hge-bin` flag.
|
||||
#
|
||||
# The Pytest runner will start a new HGE instance for each test class, with the
|
||||
# default arguments and the environment variables provided below.
|
||||
#
|
||||
# This is a work in progress.
|
||||
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
||||
|
||||
(
|
||||
cd ../..
|
||||
cabal build graphql-engine:exe:graphql-engine
|
||||
make server/tests-py/.hasura-dev-python-venv
|
||||
)
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source .hasura-dev-python-venv/bin/activate
|
||||
|
||||
docker compose rm -svf postgres
|
||||
docker compose up -d postgres
|
||||
|
||||
HASURA_GRAPHQL_PG_SOURCE_URL_1="postgresql://postgres:hasura@localhost:$(docker compose port --index 1 postgres 5432 | sd '.*:' '')/postgres"
|
||||
HASURA_GRAPHQL_PG_SOURCE_URL_2="postgresql://postgres:hasura@localhost:$(docker compose port --index 2 postgres 5432 | sd '.*:' '')/postgres"
|
||||
export HASURA_GRAPHQL_PG_SOURCE_URL_1 HASURA_GRAPHQL_PG_SOURCE_URL_2
|
||||
|
||||
export EVENT_WEBHOOK_HEADER='MyEnvValue'
|
||||
export EVENT_WEBHOOK_HANDLER='http://localhost:5592'
|
||||
export ACTION_WEBHOOK_HANDLER='http://localhost:5593'
|
||||
export SCHEDULED_TRIGGERS_WEBHOOK_DOMAIN='http://localhost:5594'
|
||||
export REMOTE_SCHEMAS_WEBHOOK_DOMAIN='http://localhost:5000'
|
||||
export GRAPHQL_SERVICE_HANDLER='http://localhost:4001'
|
||||
export GRAPHQL_SERVICE_1='http://localhost:4020'
|
||||
export GRAPHQL_SERVICE_2='http://localhost:4021'
|
||||
export GRAPHQL_SERVICE_3='http://localhost:4022'
|
||||
|
||||
pytest \
|
||||
--hge-bin="$(cabal list-bin graphql-engine:exe:graphql-engine)" \
|
||||
--pg-urls "$HASURA_GRAPHQL_PG_SOURCE_URL_1" "$HASURA_GRAPHQL_PG_SOURCE_URL_2" \
|
||||
"$@"
|
@ -24,6 +24,8 @@ class TestConfigAPI():
|
||||
jwt_conf = hge_ctx.hge_jwt_conf
|
||||
if jwt_conf is not None:
|
||||
jwt_conf_dict = json.loads(hge_ctx.hge_jwt_conf)
|
||||
else:
|
||||
jwt_conf_dict = None
|
||||
|
||||
headers = { 'x-hasura-role': 'admin' }
|
||||
if admin_secret is not None:
|
||||
@ -38,7 +40,7 @@ class TestConfigAPI():
|
||||
assert body['is_auth_hook_set'] == (auth_hook is not None)
|
||||
assert body['is_jwt_set'] == (jwt_conf is not None)
|
||||
|
||||
if jwt_conf is not None:
|
||||
if jwt_conf_dict:
|
||||
claims_format = "json"
|
||||
if 'claims_namespace_path' in jwt_conf_dict:
|
||||
assert body['jwt']['claims_namespace_path'] == jwt_conf_dict['claims_namespace_path']
|
||||
|
Loading…
Reference in New Issue
Block a user