Skip to content

Commit

Permalink
Add localclient to integration tests (#1549)
Browse files Browse the repository at this point in the history
* add localclient to integration tests

* use any_client instead of client

* fix fixtures

* fix arguments

* fix lint

* delete notebook local verify

* fix lint

* fix client lint

* rename any_client -> base_client

* export arguments file name to const

* constants
  • Loading branch information
korgan00 authored Dec 13, 2024
1 parent 77b981a commit 08168b6
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 75 deletions.
41 changes: 0 additions & 41 deletions .github/workflows/notebook-local-verify.yaml

This file was deleted.

14 changes: 11 additions & 3 deletions client/qiskit_serverless/core/clients/local_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
from qiskit_ibm_runtime import QiskitRuntimeService

from qiskit_serverless.core.constants import (
JOB_ARGUMENTS_FILE,
OT_PROGRAM_NAME,
ENV_JOB_ARGUMENTS,
)
from qiskit_serverless.core.client import BaseClient
from qiskit_serverless.core.job import (
Expand Down Expand Up @@ -112,11 +112,16 @@ def run(
**(saved_program.env_vars or {}),
**{OT_PROGRAM_NAME: saved_program.title},
**{"PATH": os.environ["PATH"]},
**{ENV_JOB_ARGUMENTS: json.dumps(arguments, cls=QiskitObjectsEncoder)},
}

with open(JOB_ARGUMENTS_FILE, "w", encoding="utf-8") as f:
json.dump(arguments, f, cls=QiskitObjectsEncoder)

with Popen(
["python", saved_program.working_dir + saved_program.entrypoint],
[
"python",
os.path.join(saved_program.working_dir, saved_program.entrypoint),
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
Expand All @@ -126,6 +131,9 @@ def run(
if pipe.wait():
status = "FAILED"
output, _ = pipe.communicate()

os.remove(JOB_ARGUMENTS_FILE)

results = re.search("\nSaved Result:(.+?):End Saved Result\n", output)
result = ""
if results:
Expand Down
2 changes: 2 additions & 0 deletions client/qiskit_serverless/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
ENV_JOB_ID_GATEWAY = "ENV_JOB_ID_GATEWAY"
ENV_JOB_ARGUMENTS = "ENV_JOB_ARGUMENTS"

JOB_ARGUMENTS_FILE = "arguments.serverless"

# artifact
MAX_ARTIFACT_FILE_SIZE_MB = 50
MAX_ARTIFACT_FILE_SIZE_MB_OVERRIDE = "MAX_ARTIFACT_FILE_SIZE_MB_OVERRIDE"
Expand Down
6 changes: 4 additions & 2 deletions client/qiskit_serverless/serializers/program_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.utils.json import RuntimeDecoder, RuntimeEncoder

from qiskit_serverless.core.constants import JOB_ARGUMENTS_FILE


class QiskitObjectsEncoder(RuntimeEncoder):
"""Json encoder for Qiskit objects."""
Expand Down Expand Up @@ -80,7 +82,7 @@ def get_arguments() -> Dict[str, Any]:
Dictionary of arguments.
"""
arguments = "{}"
if os.path.isfile("arguments.serverless"):
with open("arguments.serverless", "r", encoding="utf-8") as f:
if os.path.isfile(JOB_ARGUMENTS_FILE):
with open(JOB_ARGUMENTS_FILE, "r", encoding="utf-8") as f:
arguments = f.read()
return json.loads(arguments, cls=QiskitObjectsDecoder)
3 changes: 2 additions & 1 deletion client/tests/serializers/test_program_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from qiskit.circuit.random import random_circuit
from qiskit_ibm_runtime import QiskitRuntimeService

from qiskit_serverless.core.constants import JOB_ARGUMENTS_FILE
from qiskit_serverless.serializers.program_serializers import (
QiskitObjectsDecoder,
QiskitObjectsEncoder,
Expand Down Expand Up @@ -59,7 +60,7 @@ def test_argument_parsing(self):
circuit = random_circuit(4, 2)
array = np.array([[42.0], [0.0]])

with open("arguments.serverless", "w", encoding="utf-8") as f:
with open(JOB_ARGUMENTS_FILE, "w", encoding="utf-8") as f:
json.dump({"circuit": circuit, "array": array}, f, cls=QiskitObjectsEncoder)

parsed_arguments = get_arguments()
Expand Down
29 changes: 27 additions & 2 deletions tests/docker/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,32 @@
from pytest import fixture
from testcontainers.compose import DockerCompose
from qiskit_serverless import ServerlessClient, QiskitFunction
from qiskit_serverless.core.clients.local_client import LocalClient

resources_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "source_files"
)


@fixture(scope="module", params=["serverless", "local"])
def base_client(request):
"""Fixture for testing files with every client."""
if request.param == "serverless":
[compose, serverless] = set_up_serverless_client()
yield serverless
compose.stop()
else:
yield LocalClient()


@fixture(scope="module")
def serverless_client():
"""Fixture for testing files."""
def local_client():
"""Fixture for testing files with local client."""
return LocalClient()


def set_up_serverless_client():
"""Auxiliar fixture function to create a serverless client"""
compose = DockerCompose(
resources_path,
compose_file_name="../../../docker-compose-dev.yaml",
Expand All @@ -37,6 +54,14 @@ def serverless_client():
)
serverless.upload(function)

return [compose, serverless]


@fixture(scope="module")
def serverless_client():
"""Fixture for testing files with serverless client."""
[compose, serverless] = set_up_serverless_client()

yield serverless

compose.stop()
51 changes: 25 additions & 26 deletions tests/docker/test_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
"""Tests jobs."""
import os

from pytest import fixture, raises, mark
from pytest import raises, mark

from qiskit import QuantumCircuit
from qiskit.circuit.random import random_circuit

from qiskit_serverless import ServerlessClient, QiskitFunction
from qiskit_serverless import QiskitFunction
from qiskit_serverless.core.client import BaseClient
from qiskit_serverless.exception import QiskitServerlessException


Expand All @@ -19,26 +20,20 @@
class TestFunctionsDocker:
"""Test class for integration testing with docker."""

@fixture(scope="class")
def simple_function(self):
"""Fixture of a simple function"""
return QiskitFunction(
@mark.order(1)
def test_simple_function(self, base_client: BaseClient):
"""Integration test function uploading."""
simple_function = QiskitFunction(
title="my-first-pattern",
entrypoint="pattern.py",
working_dir=resources_path,
)

@mark.order(1)
def test_simple_function(
self, serverless_client: ServerlessClient, simple_function: QiskitFunction
):
"""Integration test function uploading."""

runnable_function = serverless_client.upload(simple_function)
runnable_function = base_client.upload(simple_function)

assert runnable_function is not None

runnable_function = serverless_client.function(simple_function.title)
runnable_function = base_client.function(simple_function.title)

assert runnable_function is not None

Expand All @@ -49,7 +44,7 @@ def test_simple_function(
assert job.status() == "DONE"
assert isinstance(job.logs(), str)

def test_function_with_arguments(self, serverless_client: ServerlessClient):
def test_function_with_arguments(self, base_client: BaseClient):
"""Integration test for Functions with arguments."""
circuit = QuantumCircuit(2)
circuit.h(0)
Expand All @@ -63,7 +58,7 @@ def test_function_with_arguments(self, serverless_client: ServerlessClient):
working_dir=resources_path,
)

runnable_function = serverless_client.upload(arguments_function)
runnable_function = base_client.upload(arguments_function)

job = runnable_function.run(circuit=circuit)

Expand All @@ -72,7 +67,7 @@ def test_function_with_arguments(self, serverless_client: ServerlessClient):
assert job.status() == "DONE"
assert isinstance(job.logs(), str)

def test_dependencies_function(self, serverless_client: ServerlessClient):
def test_dependencies_function(self, base_client: BaseClient):
"""Integration test for Functions with dependencies."""
function = QiskitFunction(
title="pattern-with-dependencies",
Expand All @@ -81,7 +76,7 @@ def test_dependencies_function(self, serverless_client: ServerlessClient):
dependencies=["pendulum"],
)

runnable_function = serverless_client.upload(function)
runnable_function = base_client.upload(function)

job = runnable_function.run()

Expand All @@ -90,7 +85,7 @@ def test_dependencies_function(self, serverless_client: ServerlessClient):
assert job.status() == "DONE"
assert isinstance(job.logs(), str)

def test_distributed_workloads(self, serverless_client: ServerlessClient):
def test_distributed_workloads(self, base_client: BaseClient):
"""Integration test for Functions for distributed workloads."""

circuits = [random_circuit(2, 2) for _ in range(3)]
Expand All @@ -102,7 +97,7 @@ def test_distributed_workloads(self, serverless_client: ServerlessClient):
entrypoint="pattern_with_parallel_workflow.py",
working_dir=resources_path,
)
runnable_function = serverless_client.upload(function)
runnable_function = base_client.upload(function)

job = runnable_function.run(circuits=circuits)

Expand All @@ -111,7 +106,7 @@ def test_distributed_workloads(self, serverless_client: ServerlessClient):
assert job.status() == "DONE"
assert isinstance(job.logs(), str)

def test_multiple_runs(self, serverless_client: ServerlessClient):
def test_multiple_runs(self, base_client: BaseClient):
"""Integration test for run functions multiple times."""

circuits = [random_circuit(2, 2) for _ in range(3)]
Expand All @@ -123,7 +118,7 @@ def test_multiple_runs(self, serverless_client: ServerlessClient):
entrypoint="pattern.py",
working_dir=resources_path,
)
runnable_function = serverless_client.upload(function)
runnable_function = base_client.upload(function)

job1 = runnable_function.run()
job2 = runnable_function.run()
Expand All @@ -133,16 +128,20 @@ def test_multiple_runs(self, serverless_client: ServerlessClient):

assert job1.job_id != job2.job_id

retrieved_job1 = serverless_client.job(job1.job_id)
retrieved_job2 = serverless_client.job(job2.job_id)
retrieved_job1 = base_client.job(job1.job_id)
retrieved_job2 = base_client.job(job2.job_id)

assert retrieved_job1.result() is not None
assert retrieved_job2.result() is not None

assert isinstance(retrieved_job1.logs(), str)
assert isinstance(retrieved_job2.logs(), str)

def test_error(self, serverless_client: ServerlessClient):
@mark.skip(
reason="Images are not working in tests jet and "
+ "LocalClient does not manage image instead of working_dir+entrypoint"
)
def test_error(self, base_client: BaseClient):
"""Integration test to force an error."""

description = """
Expand All @@ -161,7 +160,7 @@ def test_error(self, serverless_client: ServerlessClient):
description=description,
)

runnable_function = serverless_client.upload(function_with_custom_image)
runnable_function = base_client.upload(function_with_custom_image)

job = runnable_function.run(message="Argument for the custum function")

Expand Down

0 comments on commit 08168b6

Please sign in to comment.