import os
import shlex
import string
import typing
import pathlib
import tempfile
import subprocess
from importlib.metadata import version, PackageNotFoundError
import pytest
import numpy
from turbo_turtle import _settings
from turbo_turtle import _utilities
from turbo_turtle._main import get_parser
from turbo_turtle.conftest import missing_display
parser = get_parser()
subcommand_list = parser._subparsers._group_actions[0].choices.keys()
env = os.environ.copy()
turbo_turtle_command = "turbo-turtle"
try:
version("turbo_turtle")
installed = True
except PackageNotFoundError:
# TODO: Recover from the SCons task definition?
build_directory = _settings._project_root_abspath.parent / "build" / "systemtests"
build_directory.mkdir(parents=True, exist_ok=True)
installed = False
# If executing in repository, add package to PYTHONPATH and change the root command
if not installed:
turbo_turtle_command = "python -m turbo_turtle._main"
package_parent_path = _settings._project_root_abspath.parent
key = "PYTHONPATH"
if key in env:
env[key] = f"{package_parent_path}:{env[key]}"
else:
env[key] = f"{package_parent_path}"
[docs]
def setup_sphere_commands(
model,
inner_radius,
outer_radius,
angle,
y_offset,
quadrant,
element_type,
element_replacement,
backend,
output_type,
) -> typing.List[string.Template]:
"""Return the sphere/partition/mesh commands for system testing
:returns: list of string or string template commands
"""
model = pathlib.Path(model).with_suffix(".cae")
image = model.with_suffix(".png")
# TODO: Merge Gmsh sphere system tests when partition/mesh/image subcommands are implemented
# https://re-git.lanl.gov/aea/python-projects/turbo-turtle/-/issues/217
if backend == "cubit":
model = model.with_suffix(".cub")
image = image.parent / f"{image.stem}-cubit{image.suffix}"
assembly = model.stem + "_assembly.inp"
center = f"0. {y_offset} 0."
xvector = _utilities.character_delimited_list([1.0, 0.0, 0.0])
zvector = _utilities.character_delimited_list([0.0, 0.0, 1.0])
backend_option = f"--backend {backend}" if backend is not None else ""
commands = [
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--inner-radius {inner_radius} --outer-radius {outer_radius} --output-file {model} "
f"--model-name {model.stem} --part-name {model.stem} --quadrant {quadrant} "
f"--revolution-angle {angle} --y-offset {y_offset} {backend_option}"
),
string.Template(
"${turbo_turtle_command} partition --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {model} --output-file {model} "
f"--model-name {model.stem} --part-name {model.stem} --center {center} "
f"--xvector {xvector} --zvector {zvector} {backend_option}"
),
string.Template(
"${turbo_turtle_command} mesh --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {model} --output-file {model} "
f"--model-name {model.stem} --part-name {model.stem} --global-seed 0.15 "
f"--element-type {element_type} {backend_option}"
),
string.Template(
"${turbo_turtle_command} image --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {model} --output-file {image} "
f"--model-name {model.stem} --part-name {model.stem} {backend_option}"
),
string.Template(
"${turbo_turtle_command} export --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {model} --model-name {model.stem} --part-name {model.stem} "
f"--element-type {element_replacement} --destination . "
f"--assembly {assembly} --output-type {output_type} {backend_option}"
),
]
# Skip the image subcommand when DISPLAY is not found
# Skip the image subcommand when running the Genesis variations. We don't need duplicate images of the cubit meshes
if (backend == "cubit" and missing_display) or (backend == "cubit" and output_type.lower() == "genesis"):
commands.pop(3)
# Skip the partition/mesh/image/export
if inner_radius == 0:
commands = [commands[0]]
return pytest.param(commands, marks=getattr(pytest.mark, backend))
def setup_geometry_xyplot_commands(model, input_file, backend) -> typing.List[string.Template]:
part_name = _utilities.character_delimited_list(csv.stem for csv in input_file)
input_file = _utilities.character_delimited_list(input_file)
commands = [
string.Template(
"${turbo_turtle_command} geometry-xyplot "
"--abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {input_file} --output-file {model}.png --part-name {part_name} "
f"--backend {backend}"
)
]
return pytest.param(commands, marks=getattr(pytest.mark, backend))
def setup_geometry_commands(
model,
input_file,
revolution_angle,
y_offset,
backend,
) -> typing.List[string.Template]:
model = pathlib.Path(model).with_suffix(".cae")
if backend == "cubit":
model = model.with_suffix(".cub")
if backend == "gmsh":
model = model.with_suffix(".step")
part_name = " ".join(csv.stem for csv in input_file)
input_file = _utilities.character_delimited_list(input_file)
backend_option = f"--backend {backend}" if backend is not None else ""
commands = [
string.Template(
"${turbo_turtle_command} geometry --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {input_file} --model-name {model.stem} "
f"--part-name {part_name} --output-file {model} --revolution-angle {revolution_angle} "
f"--y-offset {y_offset} {backend_option}"
)
]
return pytest.param(commands, marks=getattr(pytest.mark, backend))
def setup_sets_commands(
model,
input_file,
revolution_angle,
face_sets,
edge_sets,
edge_seeds,
element_type,
backend,
) -> typing.List[string.Template]:
model = pathlib.Path(model).with_suffix(".cae")
if backend == "cubit":
model = model.with_suffix(".cub")
part_name = " ".join(csv.stem for csv in input_file)
commands = setup_geometry_commands(model, input_file, revolution_angle, 0.0, backend).values[0]
if face_sets is not None:
face_sets = _utilities.construct_append_options("--face-set", face_sets)
else:
face_sets = ""
if edge_sets is not None:
edge_sets = _utilities.construct_append_options("--edge-set", edge_sets)
else:
edge_sets = ""
if edge_seeds is not None:
edge_seeds = _utilities.construct_append_options("--edge-seed", edge_seeds)
else:
edge_seeds = ""
backend_option = f"--backend {backend}" if backend is not None else ""
sets_commands = [
string.Template(
"${turbo_turtle_command} sets --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {model} --model-name {model.stem} "
f"--part-name {part_name} --output-file {model} {face_sets} {edge_sets} {backend_option}"
),
string.Template(
"${turbo_turtle_command} mesh --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {model} --model-name {model.stem} "
f"--part-name {part_name} --output-file {model} --global-seed 1. --element-type {element_type} "
f"{edge_seeds} {backend_option}"
),
]
commands.extend(sets_commands)
return pytest.param(commands, marks=getattr(pytest.mark, backend))
def setup_cylinder_commands(model, revolution_angle, backend) -> typing.List[string.Template]:
model = pathlib.Path(model).with_suffix(".cae")
if backend == "cubit":
model = model.with_suffix(".cub")
if backend == "gmsh":
model = model.with_suffix(".step")
backend_option = f"--backend {backend}" if backend is not None else ""
commands = [
string.Template(
"${turbo_turtle_command} cylinder --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--model-name {model.stem} --part-name {model.stem} "
f"--output-file {model} --revolution-angle {revolution_angle} "
f"--inner-radius 1 --outer-radius 2 --height 1 {backend_option}"
)
]
return pytest.param(commands, marks=getattr(pytest.mark, backend))
def setup_merge_commands(part_name, backend) -> typing.List[string.Template]:
commands = []
sphere_model = pathlib.Path("merge-sphere.cae")
sphere_element_type = "C3D8"
sphere_element_replacement = "C3D8R"
geometry_model = pathlib.Path("merge-multi-part")
output_file = pathlib.Path("merge.cae")
if backend == "cubit":
sphere_model = sphere_model.with_suffix(".cub")
sphere_element_type = None
sphere_element_replacement = "HEX20"
geometry_model = geometry_model.with_suffix(".cub")
output_file = output_file.with_suffix(".cub")
# Create sphere file
sphere_options = (
str(sphere_model),
1.0,
2.0,
360.0,
0.0,
"both",
sphere_element_type,
sphere_element_replacement,
backend,
"abaqus",
)
commands.append(setup_sphere_commands(*sphere_options).values[0][0])
# Create washer/vase combined file
geometry_options = (
str(geometry_model),
[
_settings._project_root_abspath / "tests" / "washer.csv",
_settings._project_root_abspath / "tests" / "vase.csv",
],
360.0,
0.0,
backend,
)
commands.extend(setup_geometry_commands(*geometry_options).values[0])
# Run the actual merge command
part_name = f"--part-name {part_name}" if part_name else ""
backend_option = f"--backend {backend}" if backend is not None else ""
merge_command = string.Template(
"${turbo_turtle_command} merge --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {sphere_model} {geometry_model} "
f"--output-file {output_file} --merged-model-name merge "
f"--model-name merge-multi-part merge-sphere {part_name} {backend_option}"
)
commands.append(merge_command)
return pytest.param(commands, marks=getattr(pytest.mark, backend))
commands_list = []
# Legacy geometry system tests requires a series of commands before the temp directory is removed
# TODO: Decide if we should package or drop the legacy geometry tests
name = "Turbo-Turtle-Tests"
legacy_geometry_file = _settings._project_root_abspath / "tests" / "legacy_geometry.py"
commands_list.append(
pytest.param(
[
string.Template(f"${{abaqus_command}} cae -noGui {legacy_geometry_file}"),
string.Template(
"${turbo_turtle_command} partition --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {name}.cae --output-file {name}.cae --model-name {name} "
f"--part-name seveneigths-sphere --center 0 0 0 --xvector 1 0 0 --zvector 0 0 1"
),
string.Template(
"${turbo_turtle_command} image --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {name}.cae --model-name {name} "
f"--output-file seveneigths-sphere.png --part-name seveneigths-sphere"
),
string.Template(
"${turbo_turtle_command} partition --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {name}.cae --output-file {name}.cae --model-name {name} --part-name swiss-cheese "
"--center 0 0 0 --xvector 1 0 0 --zvector 0 0 1"
),
string.Template(
"${turbo_turtle_command} image --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
f"--input-file {name}.cae --model-name {name} --output-file swiss-cheese.png --part-name swiss-cheese"
),
],
marks=pytest.mark.abaqus,
)
)
# Sphere/partition/mesh
system_tests = (
# model/part, inner_radius, outer_radius, angle, y-offset, quadrant, element_type, element_replacement, backend, output_type # noqa: E501
# Abaqus CAE
("sphere.cae", 1., 2., 360., 0., "both", "C3D8", "C3D8R", "abaqus", "abaqus"), # fmt: skip # noqa: E241,E501
("solid-sphere.cae", 0., 2., 360., 0., "both", "C3D8", "C3D8R", "abaqus", "abaqus"), # fmt: skip # noqa: E241,E501
("axisymmetric.cae", 1., 2., 0., 0., "both", "CAX4", "CAX4R", "abaqus", "abaqus"), # fmt: skip # noqa: E241,E501
("quarter-sphere.cae", 1., 2., 90., 0., "both", "C3D8", "C3D8R", "abaqus", "abaqus"), # fmt: skip # noqa: E241,E501
("offset-sphere.cae", 1., 2., 360., 1., "both", "C3D8", "C3D8R", "abaqus", "abaqus"), # fmt: skip # noqa: E241,E501
("eigth-sphere.cae", 1., 2., 90., 0., "upper", "C3D8", "C3D8R", "abaqus", "abaqus"), # fmt: skip # noqa: E241,E501
("half-sphere.cae", 1., 2., 360., 0., "upper", "C3D8", "C3D8R", "abaqus", "abaqus"), # fmt: skip # noqa: E241,E501
# Cubit: for Abaqus INP
("sphere.cae", 1., 2., 360., 0., "both", None, "C3D8R", "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
("solid-sphere.cae", 0., 2., 360., 0., "both", None, "C3D8R", "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
("axisymmetric.cae", 1., 2., 0., 0., "both", None, "CAX4R", "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
("quarter-sphere.cae", 1., 2., 90., 0., "both", None, "C3D8R", "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
("offset-sphere.cae", 1., 2., 360., 1., "both", None, "C3D8R", "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
("eigth-sphere.cae", 1., 2., 90., 0., "upper", None, "C3D8R", "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
("half-sphere.cae", 1., 2., 360., 0., "upper", None, "C3D8R", "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
# Cubit "element type" is really a "meshing scheme"
("sphere-tets.cae", 1., 2., 360., 0., "both", "tetmesh", None, "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
("axisymmetric-tri.cae", 1., 2., 0., 0., "both", "trimesh", None, "cubit", "abaqus"), # fmt: skip # noqa: E241,E501
# Cubit: for Genesis INP
("sphere-genesis.cae", 1., 2., 360., 0., "both", None, "HEX", "cubit", "genesis"), # fmt: skip # noqa: E241,E501
("solid-sphere-genesis.cae", 0., 2., 360., 0., "both", None, "HEX", "cubit", "genesis"), # fmt: skip # noqa: E241,E501
("axisymmetric-genesis.cae", 1., 2., 0., 0., "both", None, "QUAD", "cubit", "genesis"), # fmt: skip # noqa: E241,E501
("quarter-sphere-genesis.cae", 1., 2., 90., 0., "both", None, "HEX", "cubit", "genesis"), # fmt: skip # noqa: E241,E501
("offset-sphere-genesis.cae", 1., 2., 360., 1., "both", None, "HEX", "cubit", "genesis"), # fmt: skip # noqa: E241,E501
("eigth-sphere-genesis.cae", 1., 2., 90., 0., "upper", None, "HEX", "cubit", "genesis"), # fmt: skip # noqa: E241,E501
("half-sphere-genesis.cae", 1., 2., 360., 0., "upper", None, "HEX", "cubit", "genesis"), # fmt: skip # noqa: E241,E501
# Cubit "element type" is really a "meshing scheme"
("sphere-tets-genesis.cae", 1., 2., 360., 0., "both", "tetmesh", "TRI", "cubit", "genesis"), # fmt: skip # noqa: E241,E501
("axisymmetric-tri-genesis.cae", 1., 2., 0., 0., "both", "trimesh", "TETRA", "cubit", "genesis"), # fmt: skip # noqa: E241,E231,E501
)
for test in system_tests:
commands_list.append(setup_sphere_commands(*test))
# Geometry XY Plot tests
system_tests = (
# model/part, input_file, backend
("washer", [_settings._project_root_abspath / "tests" / "washer.csv"], "abaqus"), # fmt: skip # noqa: E241
("vase", [_settings._project_root_abspath / "tests" / "vase.csv"], "abaqus"), # fmt: skip # noqa: E241
("multi-part", [_settings._project_root_abspath / "tests" / "washer.csv",
_settings._project_root_abspath / "tests" / "vase.csv"], "abaqus"), # fmt: skip # noqa: E241
)
for test in system_tests:
commands_list.append(setup_geometry_xyplot_commands(*test))
# Geometry tests
system_tests = (
# model/part, input_file, angle, y-offset, backend
# Abaqus
("washer", [_settings._project_root_abspath / "tests" / "washer.csv"], 360.0, 0., "abaqus"), # fmt: skip # noqa: E241,E501
("offset-washer", [_settings._project_root_abspath / "tests" / "washer.csv"], 360.0, 1., "abaqus"), # fmt: skip # noqa: E241,E501
("washer-axisymmetric", [_settings._project_root_abspath / "tests" / "washer.csv"], 0.0, 0., "abaqus"), # fmt: skip # noqa: E241,E501
("vase", [_settings._project_root_abspath / "tests" / "vase.csv"], 360.0, 0., "abaqus"), # fmt: skip # noqa: E241,E501
("vase-axisymmetric", [_settings._project_root_abspath / "tests" / "vase.csv"], 0.0, 0., "abaqus"), # fmt: skip # noqa: E241,E501
("multi-part-3D", [_settings._project_root_abspath / "tests" / "washer.csv", # fmt: skip # noqa: E241
_settings._project_root_abspath / "tests" / "vase.csv"], 360.0, 0., "abaqus"), # fmt: skip # noqa: E241,E501
("multi-part-2D", [_settings._project_root_abspath / "tests" / "washer.csv", # fmt: skip # noqa: E241
_settings._project_root_abspath / "tests" / "vase.csv"], 0.0, 0., "abaqus"), # fmt: skip # noqa: E241,E501
# Cubit
("washer", [_settings._project_root_abspath / "tests" / "washer.csv"], 360.0, 0., "cubit"), # fmt: skip # noqa: E241,E501
("offset-washer", [_settings._project_root_abspath / "tests" / "washer.csv"], 360.0, 1., "cubit"), # fmt: skip # noqa: E241,E501
("washer-axisymmetric", [_settings._project_root_abspath / "tests" / "washer.csv"], 0.0, 0., "cubit"), # fmt: skip # noqa: E241,E501
("vase", [_settings._project_root_abspath / "tests" / "vase.csv"], 360.0, 0., "cubit"), # fmt: skip # noqa: E241,E501
("vase-axisymmetric", [_settings._project_root_abspath / "tests" / "vase.csv"], 0.0, 0., "cubit"), # fmt: skip # noqa: E241,E501
("multi-part-3D", [_settings._project_root_abspath / "tests" / "washer.csv", # fmt: skip # noqa: E241
_settings._project_root_abspath / "tests" / "vase.csv"], 360.0, 0., "cubit"), # fmt: skip # noqa: E241,E501
("multi-part-2D", [_settings._project_root_abspath / "tests" / "washer.csv", # fmt: skip # noqa: E241
_settings._project_root_abspath / "tests" / "vase.csv"], 0.0, 0., "cubit"), # fmt: skip # noqa: E241,E501
# Gmsh
("washer", [_settings._project_root_abspath / "tests" / "washer.csv"], 360.0, 0., "gmsh"), # fmt: skip # noqa: E241,E501
("offset-washer", [_settings._project_root_abspath / "tests" / "washer.csv"], 360.0, 1., "gmsh"), # fmt: skip # noqa: E241,E501
("washer-axisymmetric", [_settings._project_root_abspath / "tests" / "washer.csv"], 0.0, 0., "gmsh"), # fmt: skip # noqa: E241,E501
("vase", [_settings._project_root_abspath / "tests" / "vase.csv"], 360.0, 0., "gmsh"), # fmt: skip # noqa: E241,E501
("vase-axisymmetric", [_settings._project_root_abspath / "tests" / "vase.csv"], 0.0, 0., "gmsh"), # fmt: skip # noqa: E241,E501
("multi-part-3D", [_settings._project_root_abspath / "tests" / "washer.csv", # fmt: skip # noqa: E241
_settings._project_root_abspath / "tests" / "vase.csv"], 360.0, 0., "gmsh"), # fmt: skip # noqa: E241,E501
("multi-part-2D", [_settings._project_root_abspath / "tests" / "washer.csv", # fmt: skip # noqa: E241
_settings._project_root_abspath / "tests" / "vase.csv"], 0.0, 0., "gmsh"), # fmt: skip # noqa: E241,E501
)
for test in system_tests:
commands_list.append(setup_geometry_commands(*test))
# Sets/mesh tests
system_tests = (
# model/part, input_file, angle, face_sets, edge_sets, edge seeds, element_type, backend
# Abaqus
# TODO: Pick some Abaqus edge sets for the system tests
(
"vase",
[_settings._project_root_abspath / "tests" / "vase.csv"],
360.0,
[["top", "'[#4 ]'"], ["bottom", "'[#40 ]'"]],
None,
None,
"C3D8R",
"abaqus",
),
(
"vase-axisymmetric",
[_settings._project_root_abspath / "tests" / "vase.csv"],
0.0,
None,
[["top", "'[#10 ]'"], ["bottom", "'[#1 ]'"]],
[["top", "5"], ["bottom", "0.5"]],
"CAX4",
"abaqus",
),
# Cubit
(
"vase",
[_settings._project_root_abspath / "tests" / "vase.csv"],
360.0,
[["top", "4"], ["bottom", "7"], ["outer", "'2 3 8'"]],
[["top_outer", "13"]],
[["top_outer", "1.000001"]],
"C3D8R",
"cubit",
),
(
"vase-axisymmetric",
[_settings._project_root_abspath / "tests" / "vase.csv"],
0.0,
None,
[["top", "2"], ["bottom", "6"], ["outer", "'1 7 8'"]],
[["top", "1.000001"]],
"CAX4",
"cubit",
),
)
for test in system_tests:
commands_list.append(setup_sets_commands(*test))
# Cylinder tests
system_tests = (
# model/part, angle, backend
("cylinder_3d", 360., "abaqus"), # fmt: skip # noqa: E241
("cylinder_2d", 0., "abaqus"), # fmt: skip # noqa: E241
("cylinder_3d", 360., "cubit"), # fmt: skip # noqa: E241
("cylinder_2d", 0., "cubit"), # fmt: skip # noqa: E241
("cylinder_3d", 360., "gmsh"), # fmt: skip # noqa: E241
("cylinder_2d", 0., "gmsh"), # fmt: skip # noqa: E241
)
for test in system_tests:
commands_list.append(setup_cylinder_commands(*test))
# TODO: Merge Gmsh sphere system tests when partition/mesh/image subcommands are implemented
# https://re-git.lanl.gov/aea/python-projects/turbo-turtle/-/issues/217
gmsh_sphere_2D = [
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 1. --outer-radius 2. --output-file sphere.step --revolution-angle=0. --backend gmsh"
)
],
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 1. --outer-radius 2. --output-file sphere.step --revolution-angle=0. "
"--backend gmsh --quadrant upper"
),
],
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 1. --outer-radius 2. --output-file sphere.step --revolution-angle=0. "
"--backend gmsh --quadrant lower"
),
],
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 0. --outer-radius 1. --output-file sphere.step --revolution-angle=0. --backend gmsh"
),
],
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 0. --outer-radius 1. --output-file sphere.step --revolution-angle=0. "
"--backend gmsh --quadrant upper"
),
],
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 0. --outer-radius 1. --output-file sphere.step --revolution-angle=0. "
"--backend gmsh --quadrant lower"
),
],
]
for test in gmsh_sphere_2D:
test.append(
string.Template(
"${turbo_turtle_command} mesh --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--input-file sphere.step --output-file sphere.msh --global-seed 1. --element-type unused "
"--backend gmsh"
)
)
if not missing_display:
test.append(
string.Template(
"${turbo_turtle_command} image --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--input-file sphere.step --output-file sphere.step.png --x-angle 0 --y-angle 0 --backend gmsh"
)
)
test.append(
string.Template(
"${turbo_turtle_command} image --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--input-file sphere.msh --output-file sphere.msh.png --x-angle 0 --y-angle 0 --backend gmsh"
)
)
commands_list.append(pytest.param(test, marks=pytest.mark.gmsh))
gmsh_sphere_3D = [
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 1. --outer-radius 2. --output-file sphere.step --revolution-angle=360. "
"--backend gmsh"
),
],
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 1. --outer-radius 2. --output-file sphere.step --revolution-angle=360. "
"--backend gmsh --quadrant upper"
),
],
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 1. --outer-radius 2. --output-file sphere.step --revolution-angle=360. "
"--backend gmsh --quadrant lower"
),
],
# TODO: Fix solid sphere revolve
# https://re-git.lanl.gov/aea/python-projects/turbo-turtle/-/issues/218
# [ # fmt: skip # noqa: E265
# string.Template(
# "${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
# "--inner-radius 0. --outer-radius 1. --output-file sphere.step --revolution-angle=360. "
# "--backend gmsh"
# )
# ], # fmt: skip # noqa: E265
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 0. --outer-radius 1. --output-file sphere.step --revolution-angle=360. "
"--backend gmsh --quadrant upper"
)
],
[
string.Template(
"${turbo_turtle_command} sphere --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--inner-radius 0. --outer-radius 1. --output-file sphere.step --revolution-angle=360. "
"--backend gmsh --quadrant lower"
)
],
]
for test in gmsh_sphere_3D:
test.append(
string.Template(
"${turbo_turtle_command} mesh --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--input-file sphere.step --output-file sphere.msh --global-seed 1. --element-type unused "
"--backend gmsh"
)
)
if not missing_display:
test.append(
string.Template(
"${turbo_turtle_command} image --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--input-file sphere.step --output-file sphere.step.png --x-angle 45 --y-angle -45 "
"--backend gmsh"
)
)
test.append(
string.Template(
"${turbo_turtle_command} image --abaqus-command ${abaqus_command} --cubit-command ${cubit_command} "
"--input-file sphere.msh --output-file sphere.msh.png --x-angle 45 --y-angle -45 --backend gmsh"
)
)
commands_list.append(pytest.param(test, marks=pytest.mark.gmsh))
# Merge tests
for part_name in ("washer vase merge-sphere", ""):
commands_list.append(setup_merge_commands(part_name, backend="abaqus"))
commands_list.append(setup_merge_commands(part_name, backend="cubit"))
# SCons extensions tests
# System tests as SCons tasks
# TODO: Decide how to handle this system test which requires both Abaqus and Cubit.
# Separate the SConstruct file into Abaqus/Cubit halves? Dedicated, non-matrixed construction environment?
sconstruct = _settings._project_root_abspath / "tests/SConstruct"
commands_list.append(
pytest.param(
[
string.Template(
f"scons . --sconstruct={sconstruct} --turbo-turtle-command='${{turbo_turtle_command}}' "
"--abaqus-command=${abaqus_command} --backend=abaqus"
)
],
marks=pytest.mark.abaqus,
)
)
commands_list.append(
pytest.param(
[
string.Template(
f"scons . --sconstruct={sconstruct} --turbo-turtle-command='${{turbo_turtle_command}}' "
"--cubit-command=${cubit_command} --backend=cubit"
)
],
marks=pytest.mark.cubit,
)
)
# User manual example SCons tasks
sconstruct_files = [
[
_settings._project_root_abspath / "tutorials/SConstruct",
_settings._project_root_abspath / "tutorials/SConscript",
]
]
for files in sconstruct_files:
space_delimited_files = " ".join([str(path) for path in files])
scons_test_commands = [
string.Template("${turbo_turtle_command} fetch SConstruct SConscript"),
# FIXME: Figure out why this command fails on the CI server, but not in local user tests
# https://re-git.lanl.gov/aea/python-projects/turbo-turtle/-/issues/159
# f"scons . --turbo-turtle-command="{turbo_turtle_command}"" # fmt: skip # noqa: E265
]
commands_list.append(scons_test_commands)
[docs]
@pytest.mark.systemtest
@pytest.mark.require_third_party
@pytest.mark.parametrize("commands", commands_list)
def test_require_third_party(abaqus_command, cubit_command, commands: list) -> None:
"""Run system tests that require third-party software
Executes with a temporary directory that is cleaned up after each test execution.
Accepts custom pytest CLI options to specify abaqus and cubit commands
.. code-block::
pytest --abaqus-command /my/system/abaqus --cubit-command /my/system/cubit
:param abaqus_command: string absolute path to Abaqus executable
:param cubit_command: string absolute path to Cubit executable
:param command: the full list of command string(s) for the system test
"""
test_project_shell_commands(abaqus_command, cubit_command, commands)
def run_commands(commands, build_directory, template_substitution={}) -> None:
for command in commands:
if isinstance(command, string.Template):
command = command.substitute(template_substitution)
command = shlex.split(command)
subprocess.check_output(command, env=env, cwd=build_directory).decode("utf-8")
# Help/Usage sign-of-life
project_only_commands_list = [string.Template("${turbo_turtle_command} -h")]
project_only_commands_list.extend(
[string.Template(f"${{turbo_turtle_command}} {subcommand} -h") for subcommand in subcommand_list]
)
project_only_commands_list.append(string.Template("${turbo_turtle_command} fetch"))
[docs]
@pytest.mark.systemtest
@pytest.mark.parametrize("commands", project_only_commands_list)
def test_project_shell_commands(abaqus_command, cubit_command, commands: list) -> None:
"""Run the system tests.
Executes with a temporary directory that is cleaned up after each test execution.
Accepts custom pytest CLI options to specify abaqus and cubit commands
.. code-block::
pytest --abaqus-command /my/system/abaqus --cubit-command /my/system/cubit
:param abaqus_command: string absolute path to Abaqus executable
:param cubit_command: string absolute path to Cubit executable
:param command: the full list of command string(s) for the system test
"""
template_substitution = {
"turbo_turtle_command": turbo_turtle_command,
"abaqus_command": abaqus_command,
"cubit_command": cubit_command,
}
if isinstance(commands, str) or isinstance(commands, string.Template):
commands = [commands]
if installed:
with tempfile.TemporaryDirectory() as temp_directory:
run_commands(commands, temp_directory, template_substitution=template_substitution)
else:
with tempfile.TemporaryDirectory(dir=build_directory, ignore_cleanup_errors=True) as temp_directory:
run_commands(commands, temp_directory, template_substitution=template_substitution)