diff --git a/.github/workflows/notebook-local-verify.yaml b/.github/workflows/notebook-local-verify.yaml deleted file mode 100644 index 8a280c3c0..000000000 --- a/.github/workflows/notebook-local-verify.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: Notebook LocalClient tests - -on: - pull_request: - branches: [ main ] - -permissions: - contents: read - -jobs: - tests: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #4.1.7 - - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f #5.1.1 - with: - python-version: '3.11' - - name: patch notebooks - shell: bash - run: | - for f in tests/basic/*.py; do sed -i "s/import ServerlessClient/import LocalClient/;s/= ServerlessClient(/= LocalClient(/;/token=os\.environ\.get/d;/host=os\.environ\.get/d" "$f"; done - for f in tests/experimental/*.py; do sed -i "s/import ServerlessClient/import LocalClient/;s/= ServerlessClient(/= LocalClient(/;/token=os\.environ\.get/d;/host=os\.environ\.get/d" "$f"; done - rm tests/basic/06_function.py - rm tests/experimental/file_download.py - rm tests/experimental/manage_data_directory.py - - name: install dependencies - shell: bash - run: pip install client/ - - name: Run basic notebooks - shell: bash - run: | - cd tests/basic - for f in *.py; do echo "$f" && IN_TEST=True python "$f"; done - cd - - - name: Run experimental notebooks - shell: bash - run: | - cd tests/experimental - for f in *.py; do echo "$f" && IN_TEST=True python "$f"; done - cd - diff --git a/client/qiskit_serverless/core/clients/local_client.py b/client/qiskit_serverless/core/clients/local_client.py index 237808959..d7a4966b2 100644 --- a/client/qiskit_serverless/core/clients/local_client.py +++ b/client/qiskit_serverless/core/clients/local_client.py @@ -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 ( @@ -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, @@ -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: diff --git a/client/qiskit_serverless/core/constants.py b/client/qiskit_serverless/core/constants.py index e63682e71..c495fdeb8 100644 --- a/client/qiskit_serverless/core/constants.py +++ b/client/qiskit_serverless/core/constants.py @@ -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" diff --git a/client/qiskit_serverless/serializers/program_serializers.py b/client/qiskit_serverless/serializers/program_serializers.py index a98072d9a..13f81c702 100644 --- a/client/qiskit_serverless/serializers/program_serializers.py +++ b/client/qiskit_serverless/serializers/program_serializers.py @@ -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.""" @@ -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) diff --git a/client/tests/serializers/test_program_serializers.py b/client/tests/serializers/test_program_serializers.py index 93ac794a1..16a096fd5 100644 --- a/client/tests/serializers/test_program_serializers.py +++ b/client/tests/serializers/test_program_serializers.py @@ -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, @@ -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() diff --git a/tests/docker/conftest.py b/tests/docker/conftest.py index 1507c5da9..fda2e3492 100644 --- a/tests/docker/conftest.py +++ b/tests/docker/conftest.py @@ -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", @@ -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() diff --git a/tests/docker/test_docker.py b/tests/docker/test_docker.py index 8488372e2..1911e1a78 100644 --- a/tests/docker/test_docker.py +++ b/tests/docker/test_docker.py @@ -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 @@ -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 @@ -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) @@ -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) @@ -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", @@ -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() @@ -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)] @@ -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) @@ -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)] @@ -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() @@ -133,8 +128,8 @@ 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 @@ -142,7 +137,11 @@ def test_multiple_runs(self, serverless_client: ServerlessClient): 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 = """ @@ -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")