Skip to content

Feature/optislang integration test #261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
9a36d37
update dependency groups
philipjusher Jun 24, 2025
ee24ad6
chore: adding changelog file 245.miscellaneous.md [dependabot-skip]
pyansys-ci-bot Jun 24, 2025
bf34fa8
start but stuck with getting optislang files
philipjusher Jun 24, 2025
f494131
started integration tests
philipjusher Jun 25, 2025
abc3f06
main endpoints tested just need post to components
philipjusher Jun 30, 2025
c5f5a5c
current wip for testing with pyoptislang
philipjusher Jul 8, 2025
29a4c37
added pyoptislang test
philipjusher Jul 10, 2025
88826f9
update to test workflow
philipjusher Jul 10, 2025
e1bd224
Merge branch 'main' into feature/optislang-integration-test
philipjusher Jul 10, 2025
dea4cde
update lock file after merge
philipjusher Jul 10, 2025
1b5a2ba
chore: adding changelog file 261.miscellaneous.md [dependabot-skip]
pyansys-ci-bot Jul 10, 2025
1baa9eb
moved and fixed up some tests
philipjusher Jul 10, 2025
56db682
Merge remote-tracking branch 'origin/feature/optislang-integration-te…
philipjusher Jul 10, 2025
2a129dc
chore: adding changelog file 261.maintenance.md [dependabot-skip]
pyansys-ci-bot Jul 10, 2025
4c6beb2
update to pipeline
philipjusher Jul 10, 2025
1aa8815
Merge remote-tracking branch 'origin/feature/optislang-integration-te…
philipjusher Jul 10, 2025
a0586b4
trying to get optislang from container
philipjusher Jul 10, 2025
5aa2404
updated path to tests
philipjusher Jul 10, 2025
4e7375c
moved an integration test back into correct folder
philipjusher Jul 10, 2025
d8c4d16
updating test location
philipjusher Jul 10, 2025
82ac4b8
added config to each test folder
philipjusher Jul 10, 2025
0cb8cfe
chore: adding changelog file 261.miscellaneous.md [dependabot-skip]
pyansys-ci-bot Jul 10, 2025
a9c33aa
path option didnt work
philipjusher Jul 10, 2025
d0710af
Merge remote-tracking branch 'origin/feature/optislang-integration-te…
philipjusher Jul 10, 2025
fd3a8ad
made it use local config
philipjusher Jul 10, 2025
d47759a
make parent of file as thats the directory
philipjusher Jul 10, 2025
e00bd4f
added config variables needed
philipjusher Jul 11, 2025
3bc30be
update to status retrieval and ci cd config for e2e tests
philipjusher Jul 11, 2025
9ab9510
stopped using uv
philipjusher Jul 11, 2025
d14ee88
updated to ensure pip
philipjusher Jul 11, 2025
c598c13
turn off python cache
philipjusher Jul 11, 2025
ac7af6c
trying without container
philipjusher Jul 11, 2025
efa44fe
trying some different parameters
philipjusher Jul 11, 2025
078caaa
matching ubunutu version
philipjusher Jul 11, 2025
7151590
made the same as integration tests
philipjusher Jul 11, 2025
ba36294
remove the poetry venv env variable
philipjusher Jul 11, 2025
2c9a968
using old version of pytest to see if its a version issue
philipjusher Jul 11, 2025
17478f1
back to latest version
philipjusher Jul 11, 2025
f39998b
poetry virtual envs create environment variable
philipjusher Jul 15, 2025
b266cd8
manually install poetry
philipjusher Jul 15, 2025
62d1ba8
removed dependency so it starts
philipjusher Jul 15, 2025
d7951f5
update timeout
philipjusher Jul 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ jobs:
CONCEPTEV_PASSWORD: ${{ secrets.CONCEPTEV_PASSWORD }}
POETRY_VIRTUALENVS_CREATE: ${{ vars.POETRY_VIRTUALENVS_CREATE }}
with:
pytest-extra-args: "-m 'not integration' --cov=ansys --cov-report=term --cov-report=html:.cov/html"
pytest-extra-args: "tests/unit --cov=ansys --cov-report=term --cov-report=html:.cov/html"

- name: Upload coverage results
uses: actions/upload-artifact@v4
Expand All @@ -149,12 +149,39 @@ jobs:
steps:
- uses: ansys/actions/tests-pytest@v10
env:
PYCONCEPTEV_SETTINGS: "tests/config.toml"
PYCONCEPTEV_SETTINGS: "tests/integration/config.toml"
CONCEPTEV_PASSWORD: ${{ secrets.CONCEPTEV_PASSWORD }}
POETRY_VIRTUALENVS_CREATE: ${{ vars.POETRY_VIRTUALENVS_CREATE }}
with:
python-version: ${{ env.MAIN_PYTHON_VERSION }}
pytest-extra-args: "-s -m integration --log-cli-level=INFO"
pytest-extra-args: "tests/integration"
tests-e2e:
name: E2E Tests
runs-on: ubuntu-22.04
timeout-minutes: 2160
# needs: [ tests ]
container:
image: ${{ format('ghcr.io/ansys/optislang:{0}-jammy', '25.2.0') }}
credentials:
username: ansys-bot
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: "Install Poetry"
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
installer-parallel: true
- name: "Pytest"
env:
PYOPTISLANG_DISABLE_OPTISLANG_OUTPUT: true
ANSYSLMD_LICENSE_FILE: ${{ format('1055@{0}', secrets.LICENSE_SERVER) }}
CONCEPTEV_PASSWORD: ${{ secrets.CONCEPTEV_PASSWORD }}
PYCONCEPTEV_SETTINGS: "tests/e2e/config.toml"
uses: ansys/actions/tests-pytest@v10
with:
python-version: ${{ env.MAIN_PYTHON_VERSION }}
pytest-extra-args: "tests/e2e"
doc-build:
name: Build documentation
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/261.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Feature/optislang integration test
294 changes: 140 additions & 154 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pytest-mock = "^3.12.0"

# Optional build requirements
pytest-asyncio = ">=0.24,<1.1"
ansys-optislang-core = ">=0.10,<1.1"
ansys-optislang-core = "^1.0.0"
[tool.poetry.group.build]
optional = true

Expand Down
2 changes: 2 additions & 0 deletions src/ansys/conceptev/core/ocm.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ def get_status(job_info: dict, token: str) -> str:
status = processed_response["finalStatus"].upper()
elif "lastStatus" in processed_response and processed_response["lastStatus"] is not None:
status = processed_response["lastStatus"].upper()
elif "jobStatus" in processed_response and processed_response["jobStatus"] is not None:
status = processed_response["jobStatus"][0]["jobStatus"].upper()
else:
raise ResponseError(f"Failed to get job status {processed_response}.")
return status
Expand Down
File renamed without changes.
212 changes: 212 additions & 0 deletions tests/e2e/test_optislang_e2e.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import logging
import os
import shutil

from ansys.optislang.core import Optislang
import ansys.optislang.core.node_types as node_types
from ansys.optislang.core.nodes import DesignFlow
from ansys.optislang.core.project_parametric import (
ComparisonType,
ConstraintCriterion,
ObjectiveCriterion,
)
import pytest


class QueryHandler(logging.Handler):
def __init__(self, log_filepath=None):
super().__init__()
self._collected_messages = []
self.log_filepath = log_filepath

def emit(self, record):
self._collected_messages.append(record.getMessage())
if self.log_filepath is not None:
with open(str(self.log_filepath), "a") as f:
f.write(record.getMessage() + "\n")

def get_messages(self):
collected_messages = [msg for msg in self._collected_messages]
self._collected_messages = []
return collected_messages


class LogFilter(logging.Filter):
def __init__(self, level):
super().__init__()
self._level = level

def filter(self, record):
return record.levelno <= self._level


def prepare_logging_facilities(osl_object, working_dir):
osl_object.log.setLevel(logging.DEBUG)
error_handler = QueryHandler(os.path.join(working_dir, "stderr.txt"))
error_handler.setLevel(logging.WARNING)
std_handler = QueryHandler(os.path.join(working_dir, "stdout.txt"))
std_handler.setLevel(logging.DEBUG)
std_handler.addFilter(LogFilter(logging.INFO))

osl_object.log.addHandler(std_handler)
osl_object.log.addHandler(error_handler)

return std_handler, error_handler


def register_parameter(node, name):
location = get_input_location_by_name(node, name)
node.register_location_as_parameter(
location=location,
name=name,
)


def get_input_location_by_name(node, name):
input_locations = node.get_available_input_locations()
for loc in input_locations:
if loc["location"]["name"] == name:
return loc["location"]
raise KeyError(f"Input location '{name}' not found.")


def get_output_location_by_name(node, name):
output_locations = node.get_available_output_locations()
for loc in output_locations:
if loc["location"]["name"] == name:
return loc["location"]
raise KeyError(f"Output location '{name}' not found.")


def register_response(node, name):
location = get_output_location_by_name(node, name)
node.register_location_as_response(
location=location,
name=name,
)


def get_unit_test_dir():
unit_test_dir = os.path.join(".")
return unit_test_dir


def get_working_dir():
return os.path.join(get_unit_test_dir(), "test_working_dir")


def remove_non_empty_dir(path):
if os.path.exists(path):
shutil.rmtree(path, ignore_errors=False, onerror=None)


@pytest.mark.e2e
def test_optislang_connection() -> None:
# create fresh working directory
# this is logging in interactively and connecting to prod server at the moment
working_dir = get_working_dir()
opf_filepath = os.path.join(working_dir, "test_conceptev_ci.opf")
opd_dir = os.path.join(working_dir, "test_conceptev_ci.opd")
remove_non_empty_dir(working_dir)
os.makedirs(working_dir)

osl_project_path = os.path.join(working_dir, "test_conceptev_ci.opf")

# created concept
# configure environment
design_instance_id = "121222c8-f2e8-4fa2-84fb-69336bbdc548"
osl = Optislang(project_path=osl_project_path)
print(osl)
osl.osl_server.timeouts_register.default_value = 180
std_handler, err_handler = prepare_logging_facilities(osl, working_dir)

root_system = osl.application.project.root_system
sensitivity = root_system.create_node(type_=node_types.Sensitivity)
concept_ev_node_type = node_types.NodeType(
id="conceptev",
subtype=node_types.AddinType.PYTHON_BASED_INTEGRATION_PLUGIN,
osl_class_type=node_types.NodeClassType.INTEGRATION_NODE,
)
node_name = "conceptev"
cev_node = sensitivity.create_node(
type_=concept_ev_node_type,
name=node_name,
design_flow=DesignFlow.RECEIVE_SEND,
)
non_modifying_settings = cev_node.get_property("NonModifyingSettings")
non_modifying_settings["cev_account_name"] = "ConceptEv Test Account"
cev_node.set_property("NonModifyingSettings", non_modifying_settings)
modifying_settings = cev_node.get_property("ModifyingSettings")
modifying_settings["cev_concept_id"] = design_instance_id
cev_node.set_property("ModifyingSettings", modifying_settings)
cev_node.load()

input_locations = cev_node.get_available_input_locations()
output_locations = cev_node.get_available_output_locations()
register_parameter(cev_node, "rear_motor")

register_response(cev_node, "_00__capability_curve__torque_vs_speed")
register_response(cev_node, "_02__summary__cost")
register_response(cev_node, "_02__summary__mass")
register_response(cev_node, "_02__summary__constraints_fulfilled")
register_response(cev_node, "_02__summary__n_constraints_fulfilled")

sensitivity.criteria_manager.add_criterion(
ObjectiveCriterion(
name="obj_cost", expression="_02__summary__cost", criterion=ComparisonType.MIN
)
)
sensitivity.criteria_manager.add_criterion(
ConstraintCriterion(
name="constraints_fulfilled",
expression="_02__summary__n_constraints_fulfilled",
criterion=ComparisonType.GREATEREQUAL,
limit_expression="1",
)
)

# limit the number of designs to compute
algo_settings = sensitivity.get_property("AlgorithmSettings")
algo_settings["num_discretization"] = 10
sensitivity.set_property("AlgorithmSettings", algo_settings)

# define parallelism
cev_node.set_property("MaxParallel", 10)

# define max runtime
cev_node.set_property("MaxRuntime", 180000) # milliseconds

# save
osl.application.save()

# run
osl.application.project.start()

# save and close
osl.application.save()
osl.dispose()

stderr = "".join(err_handler.get_messages())
stdout = "".join(std_handler.get_messages())
5 changes: 3 additions & 2 deletions tests/old_config.toml → tests/integration/config.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
OCM_URL = "https://dev.portal.onscale.com/api"
OCM_SOCKET_URL = "wss://sockets.dev.portal.onscale.com/socket"
CONCEPTEV_URL = "https://test-conceptev.awsansys3np.onscale.com/api"
client_id = "9c9edb71-06f9-4c4b-afed-9201668385de"
client_id = "743c94e3-c72d-4ace-b9ee-ab5c3cbe6504"
authority = "https://a365dev.b2clogin.com/a365dev.onmicrosoft.com/b2c_1a_ansysid_signup_signin_test"
scope = "https://a365dev.onmicrosoft.com/AnsysID/Authentication"
conceptev_username = "[email protected]"
account_name = "ConceptEv Test Account"
account_name = "ConceptEv Test Account"
job_timeout = 3600
1 change: 1 addition & 0 deletions tests/integration/e9.lab

Large diffs are not rendered by default.

8 changes: 0 additions & 8 deletions tests/test_ocm.py → tests/integration/test_ocm.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,18 @@ def token():
return token


@pytest.mark.integration
def test_product_id(token):
"""Test product id from OCM."""
product_id = ocm.get_product_id(token)
assert product_id == "SAAS000040"


@pytest.mark.integration
def test_get_user_id(token):
"""Test user id from OCM."""
user_id = ocm.get_user_id(token)
assert user_id == "95bb6bf9-0afd-4426-b736-7e1c8abd5a78"


@pytest.mark.integration
def test_get_account_ids(token):
"""Test account ids from OCM."""
account_ids = ocm.get_account_ids(token)
Expand All @@ -65,22 +62,19 @@ def test_get_account_ids(token):
}


@pytest.mark.integration
def test_get_account_id(token):
"""Test account ids from OCM."""
account_id = ocm.get_account_id(token)
assert account_id == "2a566ece-938d-4658-bae5-ffa387ac0547"


@pytest.mark.integration
def test_get_default_hpc(token):
"""Test default HPC from OCM."""
account_id = "2a566ece-938d-4658-bae5-ffa387ac0547"
hpc_id = ocm.get_default_hpc(token, account_id)
assert hpc_id == "3ded64e3-5a83-24a8-b6e4-9fc30f97a654"


@pytest.mark.integration
def test_get_project_ids(token):
"""Test projects from OCM."""
project_name = "New Project (with brackets)"
Expand All @@ -91,7 +85,6 @@ def test_get_project_ids(token):
assert "00932037-a633-464c-8d05-28353d9bfc49" in project_ids[project_name]


@pytest.mark.integration
def test_create_new_project(token):
"""Test create new project from OCM."""
account_id = "2a566ece-938d-4658-bae5-ffa387ac0547"
Expand All @@ -105,7 +98,6 @@ def test_create_new_project(token):
assert seconds_ago < 10


@pytest.mark.integration
def test_create_new_design(token):
"""Test create new project from OCM."""

Expand Down
Loading
Loading