diff --git a/.circleci/config.yml b/.circleci/config.yml index 4175da6c..8f8c641f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,26 +1,164 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/configuration-reference version: 2.1 -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/configuration-reference/#jobs +parameters: + preview: + type: boolean + default: true + 3-10: + type: boolean + default: true + 3-11: + type: boolean + default: true + jobs: - say-hello: - # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/configuration-reference/#executor-job + run-tests: + resource_class: small + parameters: + python-version: + type: string + default: "latest" + arangodb-version: + type: string + default: "arangodb:latest" + arangodb-config: + type: string + default: "single.conf" + cluster: + type: boolean + default: false + enterprise: + type: boolean + default: false docker: - - image: cimg/base:stable - # Add steps to the job - # See: https://circleci.com/docs/configuration-reference/#steps + - image: python:<< parameters.python-version >> + command: ["/bin/sh", "-c", "python -m http.server"] + - image: arangodb/<< parameters.arangodb-version >> + environment: + ARANGODB_CONF: << parameters.arangodb-config >> + PROJECT: /root/project + command: + - "/bin/sh" + - "-c" + - > + while ! wget -q -O /dev/null http://localhost:8000/$PROJECT/tests/static/setup.sh; do sleep 1; done && + wget -O - http://localhost:8000/$PROJECT/tests/static/setup.sh | + /bin/sh steps: - checkout - run: - name: "Say hello" - command: "echo Hello, World!" + name: "Install Dependencies" + command: | + pip install -e .[dev] + - run: + name: "Wait for ArangoDB starter" + command: | + wget --quiet --waitretry=1 --tries=120 -O - http://localhost:8528/version + if [ $? -eq 0 ]; then + echo "starter ready" + exit 0 + else + echo "starter not ready, giving up" + exit 1 + fi + - run: + name: "Run pytest" + command: | + mkdir test-results + args=("--log-cli-level=DEBUG" "--host" "localhost" "--junitxml=./test-results/junit.xml") + if [ << parameters.cluster >> = true ]; then + args+=("--cluster" "--port=8529" "--port=8539" "--port=8549") + else + args+=("--port=8529") + fi + if [ << parameters.enterprise >> = true ]; then + args+=("--enterprise") + fi + echo "Running py.test with args: ${args[@]}" + py.test "${args[@]}" + - store_test_results: + path: ./test-results/junit.xml + - store_artifacts: + path: ./test-results -# Orchestrate jobs using workflows -# See: https://circleci.com/docs/configuration-reference/#workflows workflows: - say-hello-workflow: + python-3.8-community-single-3.10: + when: << pipeline.parameters.3-10 >> + jobs: + - run-tests: + name: python-3.8-community-single-3.10 + python-version: "3.8.2" + arangodb-version: "arangodb:3.10.10" + arangodb-config: "single-3.10.conf" + cluster: false + enterprise: false + python-3.8-enterprise-cluster-3.10: + when: << pipeline.parameters.3-10 >> + jobs: + - run-tests: + name: python-3.8-enterprise-cluster-3.10 + python-version: "3.8.2" + arangodb-version: "enterprise:3.10.10" + arangodb-config: "cluster-3.10.conf" + cluster: true + enterprise: true + python-3.10-community-single-3.11: + when: << pipeline.parameters.3-11 >> + jobs: + - run-tests: + name: python-3.10-community-single-3.11 + python-version: "3.10.6" + arangodb-version: "arangodb:3.11.4" + arangodb-config: "single.conf" + cluster: false + enterprise: false + python-3.10-community-cluster-3.11: + when: << pipeline.parameters.3-11 >> + jobs: + - run-tests: + name: python-3.10-community-cluster-3.11 + python-version: "3.10.6" + arangodb-version: "arangodb:3.11.4" + arangodb-config: "cluster.conf" + cluster: true + enterprise: false + python-3.10-enterprise-single-3.11: + when: << pipeline.parameters.3-11 >> + jobs: + - run-tests: + name: python-3.10-enterprise-single-3.11 + python-version: "3.10.6" + arangodb-version: "enterprise:3.11.4" + arangodb-config: "single.conf" + cluster: false + enterprise: true + python-3.10-enterprise-cluster-3.11: + when: << pipeline.parameters.3-11 >> + jobs: + - run-tests: + name: python-3.10-enterprise-cluster-3.11 + python-version: "3.10.6" + arangodb-version: "enterprise:3.11.4" + arangodb-config: "cluster.conf" + cluster: true + enterprise: true + python-latest-enterprise-single-preview: + when: << pipeline.parameters.preview >> + jobs: + - run-tests: + name: python-latest-enterprise-single-preview + python-version: "latest" + arangodb-version: "enterprise-preview:latest" + arangodb-config: "single.conf" + cluster: false + enterprise: true + python-latest-enterprise-cluster-preview: + when: << pipeline.parameters.preview >> jobs: - - say-hello + - run-tests: + name: python-latest-enterprise-cluster-preview + python-version: "latest" + arangodb-version: "enterprise-preview:latest" + arangodb-config: "cluster.conf" + cluster: true + enterprise: true diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cdd55d25..a542f28b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,7 +2,7 @@ name: Build on: pull_request: - branches: [main, dev] + branches: [main] workflow_dispatch: inputs: debug_enabled: @@ -12,13 +12,9 @@ on: default: false jobs: - build: + docs: runs-on: ubuntu-22.04 - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] - steps: - name: Checkout repository uses: actions/checkout@v3 @@ -29,7 +25,7 @@ jobs: - name: Create ArangoDB Docker container run: > docker create --name arango -p 8529:8529 -e ARANGO_ROOT_PASSWORD=passwd -v "$(pwd)/tests/static/":/tests/static - arangodb/arangodb:3.10.9 --server.jwt-secret-keyfile=/tests/static/keyfile + arangodb/arangodb:3.11.4 --server.jwt-secret-keyfile=/tests/static/keyfile - name: Start ArangoDB Docker container run: docker start arango @@ -37,7 +33,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: '3.10' - name: Debug with tmate uses: mxschmitt/action-tmate@v3 @@ -49,18 +45,8 @@ jobs: - name: Install dependencies run: pip install .[dev] - - name: Run unit tests - run: py.test --complete --cov=arango --cov-report=xml - - name: Run Sphinx doctest run: python -m sphinx -b doctest docs docs/_build - name: Generate Sphinx HTML run: python -m sphinx -b html -W docs docs/_build - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - if: matrix.python-version == '3.10' - with: - fail_ci_if_error: false - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 23450ff4..b182474f 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -2,7 +2,7 @@ name: CodeQL on: pull_request: - branches: [main, dev] + branches: [main] schedule: - cron: '21 2 * * 3' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d25746c..6f698c30 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,10 +13,10 @@ Run unit tests with coverage: py.test --cov=arango --cov-report=html # Open htmlcov/index.html in your browser ``` -For a more comprehensive test suite, run: +To start and ArangoDB instance locally, run: ```shell -./tester.sh # Requires docker +./starter.sh # Requires docker ``` Build and test documentation: diff --git a/arango/client.py b/arango/client.py index a6bcab6d..d26352f3 100644 --- a/arango/client.py +++ b/arango/client.py @@ -15,7 +15,9 @@ from arango.exceptions import ServerConnectionError from arango.http import DEFAULT_REQUEST_TIMEOUT, DefaultHTTPClient, HTTPClient from arango.resolver import ( + FallbackHostResolver, HostResolver, + PeriodicHostResolver, RandomHostResolver, RoundRobinHostResolver, SingleHostResolver, @@ -52,9 +54,9 @@ class ArangoClient: :param hosts: Host URL or list of URLs (coordinators in a cluster). :type hosts: str | [str] :param host_resolver: Host resolver. This parameter used for clusters (when - multiple host URLs are provided). Accepted values are "roundrobin" and - "random". Any other value defaults to round robin. - :type host_resolver: str + multiple host URLs are provided). Accepted values are "fallback", + "roundrobin", "random" and "periodic". The default value is "fallback". + :type host_resolver: str | arango.resolver.HostResolver :param resolver_max_tries: Number of attempts to process an HTTP request before throwing a ConnectionAbortedError. Must not be lower than the number of hosts. @@ -88,7 +90,7 @@ class ArangoClient: def __init__( self, hosts: Union[str, Sequence[str]] = "http://127.0.0.1:8529", - host_resolver: str = "roundrobin", + host_resolver: Union[str, HostResolver] = "fallback", resolver_max_tries: Optional[int] = None, http_client: Optional[HTTPClient] = None, serializer: Callable[..., str] = default_serializer, @@ -106,10 +108,18 @@ def __init__( if host_count == 1: self._host_resolver = SingleHostResolver(1, resolver_max_tries) + elif host_resolver == "fallback": + self._host_resolver = FallbackHostResolver(host_count, resolver_max_tries) elif host_resolver == "random": self._host_resolver = RandomHostResolver(host_count, resolver_max_tries) - else: + elif host_resolver == "roundrobin": self._host_resolver = RoundRobinHostResolver(host_count, resolver_max_tries) + elif host_resolver == "periodic": + self._host_resolver = PeriodicHostResolver(host_count, resolver_max_tries) + else: + if not isinstance(host_resolver, HostResolver): + raise ValueError("Invalid host resolver") + self._host_resolver = host_resolver # Initializes the http client self._http = http_client or DefaultHTTPClient(request_timeout=request_timeout) diff --git a/arango/formatter.py b/arango/formatter.py index a74cd3ea..2058b1d6 100644 --- a/arango/formatter.py +++ b/arango/formatter.py @@ -1194,6 +1194,12 @@ def format_pregel_job_data(body: Json) -> Json: if "useMemoryMaps" in body: result["use_memory_maps"] = body["useMemoryMaps"] + # Introduced in 3.11 + if "user" in body: + result["user"] = body["user"] + if "graphLoaded" in body: + result["graph_loaded"] = body["graphLoaded"] + return verify_format(body, result) diff --git a/arango/resolver.py b/arango/resolver.py index 06a7aa77..714d76c1 100644 --- a/arango/resolver.py +++ b/arango/resolver.py @@ -1,11 +1,15 @@ __all__ = [ "HostResolver", + "FallbackHostResolver", + "PeriodicHostResolver", "SingleHostResolver", "RandomHostResolver", "RoundRobinHostResolver", ] +import logging import random +import time from abc import ABC, abstractmethod from typing import Optional, Set @@ -66,3 +70,57 @@ def __init__(self, host_count: int, max_tries: Optional[int] = None) -> None: def get_host_index(self, indexes_to_filter: Optional[Set[int]] = None) -> int: self._index = (self._index + 1) % self.host_count return self._index + + +class PeriodicHostResolver(HostResolver): + """ + Changes the host every N requests. + An optional timeout may be applied between host changes, + such that all coordinators get a chance to update their view of the agency. + For example, if one coordinator creates a database, the others may not be + immediately aware of it. If the timeout is set to 1 second, then the host + resolver waits for 1 second before changing the host. + """ + + def __init__( + self, + host_count: int, + max_tries: Optional[int] = None, + requests_period: int = 100, + switch_timeout: float = 0, + ) -> None: + super().__init__(host_count, max_tries) + self._requests_period = requests_period + self._switch_timeout = switch_timeout + self._req_count = 0 + self._index = 0 + + def get_host_index(self, indexes_to_filter: Optional[Set[int]] = None) -> int: + indexes_to_filter = indexes_to_filter or set() + self._req_count = (self._req_count + 1) % self._requests_period + if self._req_count == 0 or self._index in indexes_to_filter: + self._index = (self._index + 1) % self.host_count + while self._index in indexes_to_filter: + self._index = (self._index + 1) % self.host_count + self._req_count = 0 + time.sleep(self._switch_timeout) + return self._index + + +class FallbackHostResolver(HostResolver): + """ + Fallback host resolver. + If the current host fails, the next one is used. + """ + + def __init__(self, host_count: int, max_tries: Optional[int] = None) -> None: + super().__init__(host_count, max_tries) + self._index = 0 + self._logger = logging.getLogger(self.__class__.__name__) + + def get_host_index(self, indexes_to_filter: Optional[Set[int]] = None) -> int: + indexes_to_filter = indexes_to_filter or set() + while self._index in indexes_to_filter: + self._index = (self._index + 1) % self.host_count + self._logger.debug(f"Trying fallback on host {self._index}") + return self._index diff --git a/starter.sh b/starter.sh new file mode 100755 index 00000000..e69ce24b --- /dev/null +++ b/starter.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Starts a local ArangoDB server or cluster (community or enterprise). +# Useful for testing the python-arango driver against a local ArangoDB setup. + +# Usage: +# ./starter.sh [single|cluster] [community|enterprise] [version] +# Example: +# ./starter.sh cluster enterprise 3.11.4 + +setup="${1:-single}" +if [[ "$setup" != "single" && "$setup" != "cluster" ]]; then + echo "Invalid argument. Please provide either 'single' or 'cluster'." + exit 1 +fi + +license="${2:-all}" +if [[ "$license" != "community" && "$license" != "enterprise" ]]; then + echo "Invalid argument. Please provide either 'community' or 'enterprise'." + exit 1 +fi + +version="${3:-3.11.4}" + +if [ "$setup" == "single" ]; then + if [ "$license" == "community" ]; then + echo "Starting community single server..." + docker run -d --rm \ + --name arango \ + -p 8528:8528 \ + -p 8529:8529 \ + -v "$(pwd)/tests/static/":/tests/static \ + -v /tmp:/tmp \ + arangodb/arangodb:"$version" \ + /bin/sh -c "arangodb --configuration=/tests/static/single.conf" + elif [ "$license" == "enterprise" ]; then + echo "Starting enterprise single server..." + docker run -d --rm \ + --name arango \ + -p 8528:8528 \ + -p 8529:8529 \ + -v "$(pwd)/tests/static/":/tests/static \ + -v /tmp:/tmp \ + arangodb/enterprise:"$version" \ + /bin/sh -c "arangodb --configuration=/tests/static/single.conf" + fi +elif [ "$setup" == "cluster" ]; then + if [ "$license" == "community" ]; then + echo "Starting community cluster..." + docker run -d --rm \ + --name arango \ + -p 8528:8528 \ + -p 8529:8529 \ + -p 8539:8539 \ + -p 8549:8549 \ + -v "$(pwd)/tests/static/":/tests/static \ + -v /tmp:/tmp \ + arangodb/arangodb:"$version" \ + /bin/sh -c "arangodb --configuration=/tests/static/cluster.conf" + elif [ "$license" == "enterprise" ]; then + echo "Starting enterprise cluster..." + docker run -d --rm \ + --name arango \ + -p 8528:8528 \ + -p 8529:8529 \ + -p 8539:8539 \ + -p 8549:8549 \ + -v "$(pwd)/tests/static/":/tests/static \ + -v /tmp:/tmp \ + arangodb/enterprise:"$version" \ + /bin/sh -c "arangodb --configuration=/tests/static/cluster.conf" + fi +fi + +wget --quiet --waitretry=1 --tries=120 -O - http://localhost:8528/version | jq +if [ $? -eq 0 ]; then + echo "OK starter ready" + exit 0 +else + echo "ERROR starter not ready, giving up" + exit 1 +fi diff --git a/tester.sh b/tester.sh deleted file mode 100755 index 00371890..00000000 --- a/tester.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/bash - -# Tests python-arango driver against a local ArangoDB single server or cluster setup. -# 1. Starts a local ArangoDB server or cluster (community). -# 2. Runs the python-arango tests for the community edition. -# 3. Starts a local ArangoDB server or cluster (enterprise). -# 4. Runs all python-arango tests, including enterprise tests. - -# Usage: -# ./tester.sh [all|single|cluster] [all|community|enterprise] [version] ["notest"] - -setup="${1:-all}" -if [[ "$setup" != "all" && "$setup" != "single" && "$setup" != "cluster" ]]; then - echo "Invalid argument. Please provide either 'all', 'single' or 'cluster'." - exit 1 -fi - -tests="${2:-all}" -if [[ "$tests" != "all" && "$tests" != "community" && "$tests" != "enterprise" ]]; then - echo "Invalid argument. Please provide either 'all', 'community', or 'enterprise'." - exit 1 -fi - -# 3.11.2 -# 3.10.9 -# 3.9.11 -version="${3:-3.11.1}" - -if [[ -n "$4" && "$4" != "notest" ]]; then - echo "Invalid argument. Use 'notest' to only start the docker container, without running the tests." - exit 1 -fi -mode="${4:-test}" - -if [ "$setup" == "all" ] || [ "$setup" == "single" ]; then - if [ "$tests" == "all" ] || [ "$tests" == "community" ]; then - echo "Starting single server community setup..." - docker run -d --rm \ - --name arango \ - -p 8529:8529 \ - -v "$(pwd)/tests/static/":/tests/static \ - -v /tmp:/tmp \ - arangodb/arangodb:"$version" \ - /bin/sh -c "arangodb --configuration=/tests/static/single.conf" - - if [[ "$mode" == "notest" ]]; then - exit 0 - fi - - echo "Running python-arango tests for single server community setup..." - sleep 3 - py.test --complete --cov=arango --cov-report=html | tee single_community_results.txt - echo "Stopping single server community setup..." - docker stop arango - docker wait arango - sleep 3 - fi - - if [ "$tests" == "all" ] || [ "$tests" == "enterprise" ]; then - echo "Starting single server enterprise setup..." - docker run -d --rm \ - --name arango \ - -p 8529:8529 \ - -v "$(pwd)/tests/static/":/tests/static \ - -v /tmp:/tmp \ - arangodb/enterprise:"$version" \ - /bin/sh -c "arangodb --configuration=/tests/static/single.conf" - - if [[ "$mode" == "notest" ]]; then - exit 0 - fi - - echo "Running python-arango tests for single server enterprise setup..." - sleep 3 - py.test --complete --enterprise --cov=arango --cov-report=html --cov-append | tee single_enterprise_results.txt - echo "Stopping single server enterprise setup..." - docker stop arango - docker wait arango - sleep 3 - fi -fi - -if [ "$setup" == "all" ] || [ "$setup" == "cluster" ]; then - if [ "$tests" == "all" ] || [ "$tests" == "community" ]; then - echo "Starting community cluster setup..." - docker run -d --rm \ - --name arango \ - -p 8529:8529 \ - -v "$(pwd)/tests/static/":/tests/static \ - -v /tmp:/tmp \ - arangodb/arangodb:"$version" \ - /bin/sh -c "arangodb --configuration=/tests/static/cluster.conf" - - if [[ "$mode" == "notest" ]]; then - exit 0 - fi - - echo "Running python-arango tests for community cluster setup..." - sleep 15 - py.test --cluster --complete --cov=arango --cov-report=html | tee cluster_community_results.txt - echo "Stopping community cluster setup..." - docker stop arango - docker wait arango - sleep 3 - fi - - if [ "$tests" == "all" ] || [ "$tests" == "enterprise" ]; then - echo "Starting enterprise cluster setup..." - docker run -d --rm \ - --name arango \ - -p 8529:8529 \ - -v "$(pwd)/tests/static/":/tests/static \ - -v /tmp:/tmp \ - arangodb/enterprise:"$version" \ - /bin/sh -c "arangodb --configuration=/tests/static/cluster.conf" - - if [[ "$mode" == "notest" ]]; then - exit 0 - fi - - echo "Running python-arango tests for enterprise cluster setup..." - sleep 15 - py.test --cluster --enterprise --complete --cov=arango --cov-report=html | tee cluster_enterprise_results.txt - echo "Stopping enterprise cluster setup..." - docker stop arango - docker wait arango - fi -fi diff --git a/tests/conftest.py b/tests/conftest.py index 7ee60339..da95e2ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ from arango import ArangoClient, formatter from arango.database import StandardDatabase +from arango.http import DefaultHTTPClient from arango.typings import Json from tests.executors import TestAsyncApiExecutor, TestTransactionApiExecutor from tests.helpers import ( @@ -49,7 +50,7 @@ class GlobalData: def pytest_addoption(parser): parser.addoption("--host", action="store", default="127.0.0.1") - parser.addoption("--port", action="store", default="8529") + parser.addoption("--port", action="append", default=None) parser.addoption("--passwd", action="store", default="passwd") parser.addoption("--complete", action="store_true") parser.addoption("--cluster", action="store_true") @@ -59,15 +60,28 @@ def pytest_addoption(parser): def pytest_configure(config): - url = f"http://{config.getoption('host')}:{config.getoption('port')}" + ports = config.getoption("port") + if ports is None: + ports = ["8529"] + hosts = [f"http://{config.getoption('host')}:{p}" for p in ports] + url = hosts[0] secret = config.getoption("secret") - client = ArangoClient(hosts=[url, url, url]) + cluster = config.getoption("cluster") + + host_resolver = "fallback" + http_client = DefaultHTTPClient(request_timeout=120) + + client = ArangoClient( + hosts=hosts, host_resolver=host_resolver, http_client=http_client + ) sys_db = client.db( name="_system", username="root", password=config.getoption("passwd"), superuser_token=generate_jwt(secret), + verify=True, ) + db_version = sys_db.version() # Create a user and non-system database for testing. @@ -131,7 +145,7 @@ def pytest_configure(config): global_data.ecol_name = ecol_name global_data.fvcol_name = fvcol_name global_data.tvcol_name = tvcol_name - global_data.cluster = config.getoption("cluster") + global_data.cluster = cluster global_data.complete = config.getoption("complete") global_data.replication = config.getoption("replication") global_data.enterprise = config.getoption("enterprise") diff --git a/tests/static/cluster-3.10.conf b/tests/static/cluster-3.10.conf new file mode 100644 index 00000000..573c030a --- /dev/null +++ b/tests/static/cluster-3.10.conf @@ -0,0 +1,12 @@ +[starter] +mode = cluster +local = true +address = 0.0.0.0 +port = 8528 + +[auth] +jwt-secret = /tests/static/keyfile + +[args] +all.database.password = passwd +all.log.api-enabled = true diff --git a/tests/static/cluster.conf b/tests/static/cluster.conf index 60cfb844..182f3d17 100644 --- a/tests/static/cluster.conf +++ b/tests/static/cluster.conf @@ -2,12 +2,12 @@ mode = cluster local = true address = 0.0.0.0 +port = 8528 [auth] jwt-secret = /tests/static/keyfile [args] all.database.password = passwd -# Extended names can be used starting with 3.11 -# all.database.extended-names = true +all.database.extended-names = true all.log.api-enabled = true diff --git a/tests/static/setup.sh b/tests/static/setup.sh new file mode 100644 index 00000000..0d2189ba --- /dev/null +++ b/tests/static/setup.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +mkdir -p /tests/static +wget -O /tests/static/service.zip "http://localhost:8000/$PROJECT/tests/static/service.zip" +wget -O /tests/static/keyfile "http://localhost:8000/$PROJECT/tests/static/keyfile" +wget -O /tests/static/arangodb.conf "http://localhost:8000/$PROJECT/tests/static/$ARANGODB_CONF" +arangodb --configuration=/tests/static/arangodb.conf diff --git a/tests/static/single-3.10.conf b/tests/static/single-3.10.conf new file mode 100644 index 00000000..c982303b --- /dev/null +++ b/tests/static/single-3.10.conf @@ -0,0 +1,10 @@ +[starter] +mode = single +address = 0.0.0.0 +port = 8528 + +[auth] +jwt-secret = /tests/static/keyfile + +[args] +all.database.password = passwd diff --git a/tests/static/single.conf b/tests/static/single.conf index db6022af..e880f9d5 100644 --- a/tests/static/single.conf +++ b/tests/static/single.conf @@ -8,5 +8,4 @@ jwt-secret = /tests/static/keyfile [args] all.database.password = passwd -# Extended names can be used starting with 3.11 -# all.database.extended-names = true +all.database.extended-names = true diff --git a/tests/test_async.py b/tests/test_async.py index 61335777..0b7ca0a6 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -16,6 +16,12 @@ from tests.helpers import extract +@pytest.fixture(autouse=True) +def teardown(db): + yield + db.clear_async_jobs() + + def wait_on_job(job): """Block until the async job is done.""" while job.status() != "done": @@ -65,7 +71,10 @@ def test_async_execute_without_result(db, col, docs): assert async_col.insert(docs[2]) is None # Ensure that the operations went through - wait_on_jobs(db) + for _ in range(10): + if col.count() == 3: + break + time.sleep(0.5) assert extract("_key", col.all()) == ["1", "2", "3"] @@ -242,7 +251,7 @@ def test_async_list_jobs(db, col, docs): assert job3.id in job_ids # Test list async jobs that are pending - job4 = async_db.aql.execute("RETURN SLEEP(0.3)") + job4 = async_db.aql.execute("RETURN SLEEP(3)") assert db.async_jobs(status="pending") == [job4.id] wait_on_job(job4) # Make sure the job is done diff --git a/tests/test_client.py b/tests/test_client.py index c5018df2..5faa84db 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -10,11 +10,7 @@ from arango.database import StandardDatabase from arango.exceptions import ServerConnectionError from arango.http import DefaultHTTPClient -from arango.resolver import ( - RandomHostResolver, - RoundRobinHostResolver, - SingleHostResolver, -) +from arango.resolver import FallbackHostResolver, RandomHostResolver, SingleHostResolver from tests.helpers import generate_db_name, generate_string, generate_username @@ -40,7 +36,7 @@ def test_client_attributes(): assert client.version == importlib_metadata.version("python-arango") assert client.hosts == client_hosts assert repr(client) == client_repr - assert isinstance(client._host_resolver, RoundRobinHostResolver) + assert isinstance(client._host_resolver, FallbackHostResolver) client = ArangoClient( hosts=client_hosts, diff --git a/tests/test_cluster.py b/tests/test_cluster.py index 5702f877..5c4b8b4f 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -1,3 +1,5 @@ +import time + import pytest from packaging import version @@ -174,7 +176,15 @@ def test_cluster_rebalance(sys_db, bad_db, cluster, db_version): assert err.value.error_code == FORBIDDEN # Test rebalance execution - assert sys_db.cluster.execute_rebalance_plan(rebalance["moves"]) is True + tries = 0 + while sys_db.cluster.execute_rebalance_plan(rebalance["moves"]) is False: + if tries < 10: + tries += 1 + time.sleep(1) + else: + tries = -1 + break + assert tries != -1 with assert_raises(ClusterRebalanceError) as err: bad_db.cluster.execute_rebalance_plan(rebalance["moves"]) assert err.value.error_code == FORBIDDEN diff --git a/tests/test_database.py b/tests/test_database.py index 9ccec2c6..4fa0c0ed 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -209,7 +209,7 @@ def test_database_misc_methods(sys_db, db, bad_db, cluster): assert err.value.error_code in {11, 1228} # Test set log levels - new_levels = {"agency": "DEBUG", "collector": "INFO", "threads": "WARNING"} + new_levels = {"agency": "DEBUG", "engines": "INFO", "threads": "WARNING"} result = sys_db.set_log_levels(**new_levels) for key, value in new_levels.items(): assert result[key] == value @@ -344,7 +344,6 @@ def test_license(sys_db, enterprise): assert set(license.keys()) == { "upgrading", "features", - "hash", "license", "version", "status", diff --git a/tests/test_pregel.py b/tests/test_pregel.py index a99bddce..e17da72b 100644 --- a/tests/test_pregel.py +++ b/tests/test_pregel.py @@ -1,6 +1,7 @@ import time import pytest +from packaging import version from arango.exceptions import ( PregelJobCreateError, @@ -17,7 +18,7 @@ def test_pregel_attributes(db, username): assert repr(db.pregel) == f"" -def test_pregel_management(db, graph, cluster): +def test_pregel_management(db, db_version, graph, cluster): if cluster: pytest.skip("Not tested in a cluster setup") @@ -51,9 +52,13 @@ def test_pregel_management(db, graph, cluster): # Test delete existing pregel job assert db.pregel.delete_job(job_id) is True time.sleep(0.2) - with assert_raises(PregelJobGetError) as err: - db.pregel.job(job_id) - assert err.value.error_code in {4, 10, 1600} + if db_version < version.parse("3.11.0"): + with assert_raises(PregelJobGetError) as err: + db.pregel.job(job_id) + assert err.value.error_code in {4, 10, 1600} + else: + job = db.pregel.job(job_id) + assert job["state"] == "canceled" # Test delete missing pregel job with assert_raises(PregelJobDeleteError) as err: diff --git a/tests/test_resolver.py b/tests/test_resolver.py index ff5630a1..af58bb99 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -3,6 +3,8 @@ import pytest from arango.resolver import ( + FallbackHostResolver, + PeriodicHostResolver, RandomHostResolver, RoundRobinHostResolver, SingleHostResolver, @@ -54,3 +56,30 @@ def test_resolver_round_robin(): assert resolver.get_host_index() == 8 assert resolver.get_host_index() == 9 assert resolver.get_host_index() == 0 + + +def test_resolver_periodic(): + resolver = PeriodicHostResolver(3, requests_period=3) + assert resolver.get_host_index() == 0 + assert resolver.get_host_index() == 0 + assert resolver.get_host_index() == 1 + assert resolver.get_host_index() == 1 + assert resolver.get_host_index() == 1 + assert resolver.get_host_index() == 2 + assert resolver.get_host_index() == 2 + assert resolver.get_host_index() == 2 + assert resolver.get_host_index() == 0 + assert resolver.get_host_index() == 0 + assert resolver.get_host_index({0}) == 1 + assert resolver.get_host_index() == 1 + + +def test_resolver_fallback(): + resolver = FallbackHostResolver(4) + assert resolver.get_host_index() == 0 + assert resolver.get_host_index() == 0 + assert resolver.get_host_index({0, 1, 3}) == 2 + assert resolver.get_host_index({1, 2, 3}) == 0 + assert resolver.get_host_index({0}) == 1 + assert resolver.get_host_index({0}) == 1 + assert resolver.get_host_index() == 1