diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..4cbe2fc --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,22 @@ +name: Tests + +on: + push: + branches: [master] + pull_request: + +jobs: + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Run tests + run: pytest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 477e178..cac98c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,11 +44,12 @@ When your contribution is ready, submit a pull request. Include a clear summary ## Testing and Continuous Integration (CI) -PyRPOD uses automated testing to ensure stability and prevent regressions. To facilitate this: +PyRPOD uses [pytest](https://docs.pytest.org/) to ensure stability and prevent regressions. To facilitate this: -1. Write unit tests for your contributions in the `tests` directory. -2. Ensure all tests pass before submitting a pull request. -3. If possible, run CI tests locally before pushing your changes. Follow the instructions in the project’s CI setup documentation. +1. Write tests for your contributions in the `tests` directory, following the existing `__test_NN.py` naming convention (e.g. `rpod_unit_test_04.py`). +2. Run the full suite locally with `pytest` from the repository root before submitting a pull request. +3. Tests are automatically tagged with `unit`, `integration`, `verification`, and subsystem (`mdao`, `mission`, `plume`, `rpod`) markers based on their filename, so you can run a subset with e.g. `pytest -m unit` or `pytest -m rpod`. +4. CI runs the same `pytest` suite automatically on every push and pull request via [GitHub Actions](.github/workflows/tests.yml). ## Code Formatting diff --git a/README.md b/README.md index f35c873..6ad766d 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,21 @@ PyRPOD utilizies scientific libraries such as NumPy, SciPy, Matplotlib, and SymP pip install -r requirements.txt ``` -3. Run all test cases. +3. Run all test cases. ```bash - cd tests/ - python3 test_all.py + python -m pytest ``` + It is recomended to use `python -m pytest` rather than the bare `pytest` command. This guarantees the + `python` you're currently using (venv, conda env, etc.) runs the tests, avoiding + `ModuleNotFoundError`s that show up if a system-wide `pytest` (e.g. installed via + `apt`) ends up on your `PATH` instead of the one in the environment where you ran + `pip install -r requirements.txt`. -3. Run individual test cases (Not ideal, I know). +4. Run a subset of test cases using markers (auto-tagged by category and subsystem). ```bash - cd tests/ - mv /test_case.py . - python3 test_case.py + python -m pytest -m unit # unit tests only + python -m pytest -m integration # integration tests only + python -m pytest -m verification # verification tests only + python -m pytest -m rpod # a specific subsystem (mdao, mission, plume, rpod) + python -m pytest tests/rpod # or just point pytest at a directory/file directly ``` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d8d79e5..7867429 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,3 +24,18 @@ show_error_codes = true "tests/**" = ["D", "S101"] "**/__pycache__/**" = ["ALL"] "venv-pyrpod/**" = ["ALL"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +# Legacy test files are named "__test_NN.py" rather than pytest's default patterns. +python_files = ["test_*.py", "*_test_*.py"] +pythonpath = ["."] +markers = [ + "unit: unit-level test cases", + "integration: integration test cases", + "verification: verification/validation test cases", + "mdao: mdao subsystem tests", + "mission: mission subsystem tests", + "plume: plume subsystem tests", + "rpod: rpod subsystem tests", +] diff --git a/requirements.txt b/requirements.txt index 12287aa..6623d7b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,5 @@ mypy black ruff flake8 -isort \ No newline at end of file +isort +pytest \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..42d0703 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,40 @@ +import os +from pathlib import Path + +import pytest + +TESTS_DIR = Path(__file__).resolve().parent + +CATEGORIES = ("unit", "integration", "verification") +GROUPS = ("mdao", "mission", "plume", "rpod") + +# These files import a flat `pyrpod.` layout that no longer exists after +# the rpod -> plume/vehicle refactor. Excluded from collection until they are +# fixed or removed outright. +collect_ignore = ["old", "test_case_25.py", "rpod_verification_test_05.py"] + + +@pytest.fixture(autouse=True, scope="session") +def _run_from_tests_dir(): + # Legacy test cases assume the process CWD is this `tests/` directory, + # e.g. `case_dir = '../case/...'` and `open('rpod/rpod_int_test_02...')`. + # Reproduce that historical invocation so paths resolve without editing + # every test file's path strings. + previous_cwd = os.getcwd() + os.chdir(TESTS_DIR) + yield + os.chdir(previous_cwd) + + +def pytest_collection_modifyitems(items): + for item in items: + stem = Path(item.fspath).stem + relative_parts = Path(item.fspath).resolve().relative_to(TESTS_DIR).parts + + for category in CATEGORIES: + if f"_{category}_test" in stem: + item.add_marker(getattr(pytest.mark, category)) + + for group in GROUPS: + if group in relative_parts or stem.startswith(f"{group}_"): + item.add_marker(getattr(pytest.mark, group)) diff --git a/tests/mdao/mdao_integration_test_01.py b/tests/mdao/mdao_integration_test_01.py index 254a9f0..f0b8dfe 100644 --- a/tests/mdao/mdao_integration_test_01.py +++ b/tests/mdao/mdao_integration_test_01.py @@ -9,7 +9,6 @@ # ======================== # Write test case description. -import test_header import unittest class MDAOTest(unittest.TestCase): diff --git a/tests/mdao/mdao_unit_test_01.py b/tests/mdao/mdao_unit_test_01.py index 02234d5..5647c46 100644 --- a/tests/mdao/mdao_unit_test_01.py +++ b/tests/mdao/mdao_unit_test_01.py @@ -9,7 +9,6 @@ # ======================== # Write test case description. -import test_header import unittest class MDAOTest(unittest.TestCase): diff --git a/tests/mdao/mdao_unit_test_02.py b/tests/mdao/mdao_unit_test_02.py index a7c8ec8..1c5b4ef 100644 --- a/tests/mdao/mdao_unit_test_02.py +++ b/tests/mdao/mdao_unit_test_02.py @@ -1,101 +1,100 @@ -# Juan P. Roldan -# University of Central Florida -# Department of Mechanical and Aerospace Engineering -# Last Changed: 03-28-24 - -# ======================== -# PyRPOD: test/mdao/mdao_unit_test_03.py -# ======================== -# A test case to create array of cant angle swept thruster configurations. -# The sweep assumes the given thrusters angle symmetrically. -# This means that opposite pitch thrusters angle opposite but equally to each other -# and yaw thrusters angle opposite but equally to each other -# !!currently, the pitch and yaw thrusters are canted in unison!! -# pitch thrusters are not angled such that they can produce a yaw -# yaw thrusters are not angled such that they can introduce a pitch - -# TODO: Re-factor code to save data in a relevant object. Also add files to save to. - -import test_header -import unittest, os, sys -import numpy as np - -from pyrpod.rpod import JetFiringHistory, PlumeStrikeEstimationStudy -from pyrpod.vehicle import TargetVehicle, VisitingVehicle -from pyrpod.mdao import SweepConfig - - -class CoordinateSweepCheck(unittest.TestCase): - def test_cant_sweep(self): - - # # number of deceleration thrusters to evenly distribute - # # !!!IMPORTANT!!! also ensure to update jfh, fire all thrusters one wants to visualize!!! - # nthrusters = 7 - - # # define the LM's radius - # r = 2 # m - - # # creating an example configuration - - # # identity matrix as the default for all thrusters - # # techically not needed, since all the thrusters are set to -x group - # # these thrusters DCMs are standardized within SweepConfig - # dcm = np.eye(3) - - # # creating example thruster groups, to confirm thrusters that are for decel - # thruster_groups = { - # '+x': [], - # 'neg_x': [], - # '+y': [], - # '-y': [], - # '+z': [], - # '-z': [], - # '+pitch': [], - # '-pitch': [], - # '+yaw' : [], - # '-yaw' : [] - # } - - # config = {} - - # # name each thruster, as corresponding to different packs - # # evenly distribute the exit coordinate of each thruster - # # append each thruster to deceleration group - # for i in range(1, nthrusters+1): - # name = 'P' + str(i) + 'T1' - # exit = [0, r*np.cos((i-1)*(2 * np.pi / nthrusters)), r*np.sin((i-1)*(2 * np.pi / nthrusters))] - # config[name] = {'name': [name], 'type': ['001'], 'exit': [exit], 'dcm': dcm} - # thruster_groups['neg_x'].append(name) - - # # define step sizes for each angle - # dcant = 10 # deg - - # # create SweepAngles object - # angle_sweep = SweepConfig.SweepDecelAngles(config, thruster_groups) - - # # call sweep_long_thruster on the configuration and print the DCM's - # config_swept_array = angle_sweep.sweep_decel_thrusters_all(dcant) - - # for i, config in enumerate(config_swept_array): - # # Path to directory holding data assets and results for a specific RPOD study. - # case_dir = '../case/mdao/cant_sweep/' - - # # Load JFH data. - # jfh = JetFiringHistory.JetFiringHistory(case_dir) - # jfh.read_jfh() - - # tv = TargetVehicle.TargetVehicle(case_dir) - # tv.set_stl() - - # vv = VisitingVehicle.VisitingVehicle(case_dir) - # vv.set_stl() - # vv.set_thruster_config(config) - - # rpod = RPOD.RPOD(case_dir) - # rpod.study_init(jfh, tv, vv) - - # rpod.visualize_sweep(i) - return - -if __name__ == '__main__': +# Juan P. Roldan +# University of Central Florida +# Department of Mechanical and Aerospace Engineering +# Last Changed: 03-28-24 + +# ======================== +# PyRPOD: test/mdao/mdao_unit_test_03.py +# ======================== +# A test case to create array of cant angle swept thruster configurations. +# The sweep assumes the given thrusters angle symmetrically. +# This means that opposite pitch thrusters angle opposite but equally to each other +# and yaw thrusters angle opposite but equally to each other +# !!currently, the pitch and yaw thrusters are canted in unison!! +# pitch thrusters are not angled such that they can produce a yaw +# yaw thrusters are not angled such that they can introduce a pitch + +# TODO: Re-factor code to save data in a relevant object. Also add files to save to. + +import unittest, os, sys +import numpy as np + +from pyrpod.rpod import JetFiringHistory, PlumeStrikeEstimationStudy +from pyrpod.vehicle import TargetVehicle, VisitingVehicle +from pyrpod.mdao import SweepConfig + + +class CoordinateSweepCheck(unittest.TestCase): + def test_cant_sweep(self): + + # # number of deceleration thrusters to evenly distribute + # # !!!IMPORTANT!!! also ensure to update jfh, fire all thrusters one wants to visualize!!! + # nthrusters = 7 + + # # define the LM's radius + # r = 2 # m + + # # creating an example configuration + + # # identity matrix as the default for all thrusters + # # techically not needed, since all the thrusters are set to -x group + # # these thrusters DCMs are standardized within SweepConfig + # dcm = np.eye(3) + + # # creating example thruster groups, to confirm thrusters that are for decel + # thruster_groups = { + # '+x': [], + # 'neg_x': [], + # '+y': [], + # '-y': [], + # '+z': [], + # '-z': [], + # '+pitch': [], + # '-pitch': [], + # '+yaw' : [], + # '-yaw' : [] + # } + + # config = {} + + # # name each thruster, as corresponding to different packs + # # evenly distribute the exit coordinate of each thruster + # # append each thruster to deceleration group + # for i in range(1, nthrusters+1): + # name = 'P' + str(i) + 'T1' + # exit = [0, r*np.cos((i-1)*(2 * np.pi / nthrusters)), r*np.sin((i-1)*(2 * np.pi / nthrusters))] + # config[name] = {'name': [name], 'type': ['001'], 'exit': [exit], 'dcm': dcm} + # thruster_groups['neg_x'].append(name) + + # # define step sizes for each angle + # dcant = 10 # deg + + # # create SweepAngles object + # angle_sweep = SweepConfig.SweepDecelAngles(config, thruster_groups) + + # # call sweep_long_thruster on the configuration and print the DCM's + # config_swept_array = angle_sweep.sweep_decel_thrusters_all(dcant) + + # for i, config in enumerate(config_swept_array): + # # Path to directory holding data assets and results for a specific RPOD study. + # case_dir = '../case/mdao/cant_sweep/' + + # # Load JFH data. + # jfh = JetFiringHistory.JetFiringHistory(case_dir) + # jfh.read_jfh() + + # tv = TargetVehicle.TargetVehicle(case_dir) + # tv.set_stl() + + # vv = VisitingVehicle.VisitingVehicle(case_dir) + # vv.set_stl() + # vv.set_thruster_config(config) + + # rpod = RPOD.RPOD(case_dir) + # rpod.study_init(jfh, tv, vv) + + # rpod.visualize_sweep(i) + return + +if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/mdao/mdao_verification_test_04.py b/tests/mdao/mdao_verification_test_04.py index 220e31f..55d94ed 100644 --- a/tests/mdao/mdao_verification_test_04.py +++ b/tests/mdao/mdao_verification_test_04.py @@ -10,7 +10,6 @@ # Initial variable sweep study used to explore the maximum overshoot velocity an LM can handle # if the thruster configuration and decelaration starting distance are held constant. -import test_header import unittest from pyrpod.vehicle import TargetVehicle, LogisticsModule diff --git a/tests/mdao/mdao_verification_test_05.py b/tests/mdao/mdao_verification_test_05.py index 7655598..ebb8db1 100644 --- a/tests/mdao/mdao_verification_test_05.py +++ b/tests/mdao/mdao_verification_test_05.py @@ -9,7 +9,6 @@ # ======================== # -import test_header import unittest from pyrpod.vehicle import TargetVehicle, LogisticsModule diff --git a/tests/mdao/mdao_verification_test_06.py b/tests/mdao/mdao_verification_test_06.py index 93f531d..0eeae5e 100644 --- a/tests/mdao/mdao_verification_test_06.py +++ b/tests/mdao/mdao_verification_test_06.py @@ -9,7 +9,6 @@ # ======================== # -import test_header import unittest from pyrpod.vehicle import TargetVehicle, LogisticsModule diff --git a/tests/mission/mission_integration_test_01.py b/tests/mission/mission_integration_test_01.py index dc1dca9..3d688fc 100644 --- a/tests/mission/mission_integration_test_01.py +++ b/tests/mission/mission_integration_test_01.py @@ -8,7 +8,6 @@ # ======================== # A brief test case to calculate the 6DOF performance of each individual thruster in the LM -import test_header import unittest, os, sys import numpy as np diff --git a/tests/mission/mission_integration_test_02.py b/tests/mission/mission_integration_test_02.py index a1b09ac..52a8907 100644 --- a/tests/mission/mission_integration_test_02.py +++ b/tests/mission/mission_integration_test_02.py @@ -8,7 +8,6 @@ # ======================== # A brief test case to calculate the 6DOF performance of thruster working groups -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule diff --git a/tests/mission/mission_integration_test_03.py b/tests/mission/mission_integration_test_03.py index 8d78182..fa93ab9 100644 --- a/tests/mission/mission_integration_test_03.py +++ b/tests/mission/mission_integration_test_03.py @@ -8,7 +8,6 @@ # ======================== # A brief test case to calculate RCS perfomance for a given flight plan approximating Δv requirements. -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule from pyrpod.mission import MissionPlanner, MissionEnvironment diff --git a/tests/mission/mission_integration_test_04.py b/tests/mission/mission_integration_test_04.py index 8064b18..c24ce7c 100644 --- a/tests/mission/mission_integration_test_04.py +++ b/tests/mission/mission_integration_test_04.py @@ -8,7 +8,6 @@ # ======================== # Test case to analyze notional (1D transation + rotation) approach. (NEEDS TLC) -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule from pyrpod.mission import MissionPlanner, MissionEnvironment diff --git a/tests/mission/mission_integration_test_05.py b/tests/mission/mission_integration_test_05.py index d88bb4a..e6ffe26 100644 --- a/tests/mission/mission_integration_test_05.py +++ b/tests/mission/mission_integration_test_05.py @@ -20,7 +20,6 @@ # 3. Use time and distance limits to create flight envelope data. # 4. Add data points for relevant thruster technologies. -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule from pyrpod.mission import MissionPlanner, MissionEnvironment diff --git a/tests/mission/mission_integration_test_06.py b/tests/mission/mission_integration_test_06.py index 3c89a38..caef5df 100644 --- a/tests/mission/mission_integration_test_06.py +++ b/tests/mission/mission_integration_test_06.py @@ -22,7 +22,6 @@ # 4. Add data points for relevant thruster technologies. # 5. Given a delta V and W requirement, comparison of thrust. isp, and mass flow rate. (M3) (Fuel Usage) -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule from pyrpod.mission import MissionPlanner, MissionEnvironment diff --git a/tests/mission/mission_integration_test_07.py b/tests/mission/mission_integration_test_07.py index 7980c08..fc6d83b 100644 --- a/tests/mission/mission_integration_test_07.py +++ b/tests/mission/mission_integration_test_07.py @@ -8,7 +8,6 @@ # ======================== # Test case to contour the burn plot graph across various thrust and ISP values. (NEEDS TLC) -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule from pyrpod.mission import MissionPlanner, MissionEnvironment diff --git a/tests/mission/mission_integration_test_08.py b/tests/mission/mission_integration_test_08.py index 3c3418f..390aed7 100644 --- a/tests/mission/mission_integration_test_08.py +++ b/tests/mission/mission_integration_test_08.py @@ -8,7 +8,6 @@ # ======================== # Test case to contour the propellant usage across the various Δv in a given flight plan. -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule from pyrpod.mission import MissionPlanner, MissionEnvironment diff --git a/tests/mission/mission_unit_test_01.py b/tests/mission/mission_unit_test_01.py index 43d2f59..041e405 100644 --- a/tests/mission/mission_unit_test_01.py +++ b/tests/mission/mission_unit_test_01.py @@ -9,7 +9,6 @@ # ======================== # Write test case description. -import test_header import unittest class MDAOTest(unittest.TestCase): diff --git a/tests/mission/mission_verification_test_01.py b/tests/mission/mission_verification_test_01.py index bf3f5f8..ca3ff9e 100644 --- a/tests/mission/mission_verification_test_01.py +++ b/tests/mission/mission_verification_test_01.py @@ -10,7 +10,6 @@ # Write test case description. -import test_header import unittest class MDAOTest(unittest.TestCase): diff --git a/tests/mission/mission_verification_test_02.py b/tests/mission/mission_verification_test_02.py index 4c49d2e..25305ba 100644 --- a/tests/mission/mission_verification_test_02.py +++ b/tests/mission/mission_verification_test_02.py @@ -10,7 +10,6 @@ # Write test case description. -import test_header import unittest from pyrpod.mission import MissionPlanner, MissionEnvironment diff --git a/tests/plume/plume_integration_test_01.py b/tests/plume/plume_integration_test_01.py index 7063936..5906e0e 100644 --- a/tests/plume/plume_integration_test_01.py +++ b/tests/plume/plume_integration_test_01.py @@ -9,7 +9,6 @@ # ======================== # Write test case description. -import test_header import unittest class MDAOTest(unittest.TestCase): diff --git a/tests/plume/plume_unit_test_01.py b/tests/plume/plume_unit_test_01.py index 6f34365..a11712e 100644 --- a/tests/plume/plume_unit_test_01.py +++ b/tests/plume/plume_unit_test_01.py @@ -9,7 +9,6 @@ # ======================== # Write test case description. -import test_header import unittest class PlumeTest(unittest.TestCase): diff --git a/tests/plume/plume_verification_test_01.py b/tests/plume/plume_verification_test_01.py index baa2b74..9cdc459 100644 --- a/tests/plume/plume_verification_test_01.py +++ b/tests/plume/plume_verification_test_01.py @@ -9,7 +9,6 @@ # A test case to plot simple radial expansion profiles. # TODO: Re-factor code to save data in a relevant object. Also add files to save to. -import test_header import unittest, os, sys from pyrpod.plume import IsentropicExpansion diff --git a/tests/rpod/rpod_integration_test_01.py b/tests/rpod/rpod_integration_test_01.py index 5efd314..32f02e2 100644 --- a/tests/rpod/rpod_integration_test_01.py +++ b/tests/rpod/rpod_integration_test_01.py @@ -1,144 +1,143 @@ -import logging -logging.basicConfig(filename='rpod_integration_test_01.log', level=logging.INFO, format='%(message)s') - -# Andy Torres, Nicholas Palumbo -# Last Changed: 11-17-24 - -# ======================== -# PyRPOD: tests/rpod/rpod_integration_test_01.py -# ======================== -# This test asserts the expected number of cell strikes on a flat plate STL -# for a notional trajectory meant to reperesent a "sweep" above it. This -# is also established as the base case for RPOD plume impingement analysis. -# The test uses JFH data to assert expected strike counts across 20 distinct firings. - -import test_header -import unittest, os, sys -from pyrpod.vehicle import LogisticsModule, TargetVehicle -from pyrpod.rpod import JetFiringHistory, PlumeStrikeEstimationStudy -from pyrpod.mission import MissionEnvironment - -class BaseCaseChecks(unittest.TestCase): - def test_base_case(self): - - # 1. Set Up - # Path to directory holding data assets and results for a specific RPOD study. - case_dir = '../case/rpod/base_case/' - - # Instantiate JetFiringHistory object. - jfh = JetFiringHistory.JetFiringHistory(case_dir) - - # Load Target Vehicle. - tv = TargetVehicle.TargetVehicle(case_dir) - tv.set_stl() - - # Instantiate LogisticModule object. - lm = LogisticsModule.LogisticsModule(case_dir) - - # Load in thruster configuration file. - lm.set_thruster_config() - - # Set mission environment. - me = MissionEnvironment.MissionEnvironment(case_dir) - - # Instantiate RPOD object. - plume_strike_study = PlumeStrikeEstimationStudy.PlumeStrikeEstimationStudy(me) - plume_strike_study.study_init(jfh, tv, lm) - - # Read in JFH. - jfh.read_jfh() - - # 2. Execute - # Conduct RPOD analysis - plume_strike_study.graph_jfh() - strikes = plume_strike_study.jfh_plume_strikes() - - # 3. Assert - # Assert expected strike values for each firing in the JFH. - expected_strikes = { - '1': 282.0, - '2': 279.0, - '3': 285.0, - '4': 282.0, - '5': 280.0, - '6': 284.0, - '7': 281.0, - '8': 280.0, - '9': 282.0, - '10': 280.0, - '11': 280.0, - '12': 277.0, - '13': 283.0, - '14': 282.0, - '15': 279.0, - '16': 285.0, - '17': 282.0, - '18': 280.0, - '19': 284.0, - '20': 282.0 - } - - # Assert expected cumulative strike values for each firing in the JFH. - expected_cum_strikes = { - '1': 282.0, - '2': 561.0, - '3': 846.0, - '4': 1128.0, - '5': 1408.0, - '6': 1692.0, - '7': 1973.0, - '8': 2253.0, - '9': 2535.0, - '10': 2815.0, - '11': 3095.0, - '12': 3372.0, - '13': 3655.0, - '14': 3937.0, - '15': 4216.0, - '16': 4501.0, - '17': 4783.0, - '18': 5063.0, - '19': 5347.0, - '20': 5629.0 - } - - # Read in expected strikes from text file. - file_path = 'rpod/rpod_int_test_01_expected_strikes.log' - expected_strike_ids = {} - with open(file_path, 'r') as file: - file_content = file.readlines() - - cur_firing = '' - - for line in file_content: - # Make a an array of data to orgnaize strike data by firing. - if 'n_firing' in line: - cur_firing = str(line.split()[1]) - expected_strike_ids[cur_firing] = [] - else: - expected_strike_ids[cur_firing].append(int(line)) - - for n_firing in strikes.keys(): - # Development statements used to write comparison entries in expected_strikes - # logging.info('n_firing ' + str(n_firing)) - for i in range(len(strikes[n_firing]['strikes'])): - if strikes[n_firing]['strikes'][i] > 0: - # string = 'strikes[' + str(i) + '] = ' + str(strikes[n_firing]['cum_strikes'][i]) - # logging.info(string) - - # logging.info(str(i)) - self.assertIn(i, expected_strike_ids[n_firing]) - # Development statements used to write comparison entries in expected_strikes - string = '\''+str(n_firing)+'\': ' + ' ' +str(strikes[n_firing]['cum_strikes'].sum()) +',' - logging.info(string) - - # Number of strikes for a given time step. - n_strikes = strikes[n_firing]['strikes'].sum() - n_cum_strikes = strikes[n_firing]['cum_strikes'].sum() - - # Assert that it matches the expected value. - self.assertEqual(n_strikes, expected_strikes[n_firing]) - self.assertEqual(n_cum_strikes, expected_cum_strikes[n_firing]) - -if __name__ == '__main__': +import logging +logging.basicConfig(filename='rpod_integration_test_01.log', level=logging.INFO, format='%(message)s') + +# Andy Torres, Nicholas Palumbo +# Last Changed: 11-17-24 + +# ======================== +# PyRPOD: tests/rpod/rpod_integration_test_01.py +# ======================== +# This test asserts the expected number of cell strikes on a flat plate STL +# for a notional trajectory meant to reperesent a "sweep" above it. This +# is also established as the base case for RPOD plume impingement analysis. +# The test uses JFH data to assert expected strike counts across 20 distinct firings. + +import unittest, os, sys +from pyrpod.vehicle import LogisticsModule, TargetVehicle +from pyrpod.rpod import JetFiringHistory, PlumeStrikeEstimationStudy +from pyrpod.mission import MissionEnvironment + +class BaseCaseChecks(unittest.TestCase): + def test_base_case(self): + + # 1. Set Up + # Path to directory holding data assets and results for a specific RPOD study. + case_dir = '../case/rpod/base_case/' + + # Instantiate JetFiringHistory object. + jfh = JetFiringHistory.JetFiringHistory(case_dir) + + # Load Target Vehicle. + tv = TargetVehicle.TargetVehicle(case_dir) + tv.set_stl() + + # Instantiate LogisticModule object. + lm = LogisticsModule.LogisticsModule(case_dir) + + # Load in thruster configuration file. + lm.set_thruster_config() + + # Set mission environment. + me = MissionEnvironment.MissionEnvironment(case_dir) + + # Instantiate RPOD object. + plume_strike_study = PlumeStrikeEstimationStudy.PlumeStrikeEstimationStudy(me) + plume_strike_study.study_init(jfh, tv, lm) + + # Read in JFH. + jfh.read_jfh() + + # 2. Execute + # Conduct RPOD analysis + plume_strike_study.graph_jfh() + strikes = plume_strike_study.jfh_plume_strikes() + + # 3. Assert + # Assert expected strike values for each firing in the JFH. + expected_strikes = { + '1': 282.0, + '2': 279.0, + '3': 285.0, + '4': 282.0, + '5': 280.0, + '6': 284.0, + '7': 281.0, + '8': 280.0, + '9': 282.0, + '10': 280.0, + '11': 280.0, + '12': 277.0, + '13': 283.0, + '14': 282.0, + '15': 279.0, + '16': 285.0, + '17': 282.0, + '18': 280.0, + '19': 284.0, + '20': 282.0 + } + + # Assert expected cumulative strike values for each firing in the JFH. + expected_cum_strikes = { + '1': 282.0, + '2': 561.0, + '3': 846.0, + '4': 1128.0, + '5': 1408.0, + '6': 1692.0, + '7': 1973.0, + '8': 2253.0, + '9': 2535.0, + '10': 2815.0, + '11': 3095.0, + '12': 3372.0, + '13': 3655.0, + '14': 3937.0, + '15': 4216.0, + '16': 4501.0, + '17': 4783.0, + '18': 5063.0, + '19': 5347.0, + '20': 5629.0 + } + + # Read in expected strikes from text file. + file_path = 'rpod/rpod_int_test_01_expected_strikes.log' + expected_strike_ids = {} + with open(file_path, 'r') as file: + file_content = file.readlines() + + cur_firing = '' + + for line in file_content: + # Make a an array of data to orgnaize strike data by firing. + if 'n_firing' in line: + cur_firing = str(line.split()[1]) + expected_strike_ids[cur_firing] = [] + else: + expected_strike_ids[cur_firing].append(int(line)) + + for n_firing in strikes.keys(): + # Development statements used to write comparison entries in expected_strikes + # logging.info('n_firing ' + str(n_firing)) + for i in range(len(strikes[n_firing]['strikes'])): + if strikes[n_firing]['strikes'][i] > 0: + # string = 'strikes[' + str(i) + '] = ' + str(strikes[n_firing]['cum_strikes'][i]) + # logging.info(string) + + # logging.info(str(i)) + self.assertIn(i, expected_strike_ids[n_firing]) + # Development statements used to write comparison entries in expected_strikes + string = '\''+str(n_firing)+'\': ' + ' ' +str(strikes[n_firing]['cum_strikes'].sum()) +',' + logging.info(string) + + # Number of strikes for a given time step. + n_strikes = strikes[n_firing]['strikes'].sum() + n_cum_strikes = strikes[n_firing]['cum_strikes'].sum() + + # Assert that it matches the expected value. + self.assertEqual(n_strikes, expected_strikes[n_firing]) + self.assertEqual(n_cum_strikes, expected_cum_strikes[n_firing]) + +if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/rpod/rpod_integration_test_02.py b/tests/rpod/rpod_integration_test_02.py index b44a9ca..db5c69f 100644 --- a/tests/rpod/rpod_integration_test_02.py +++ b/tests/rpod/rpod_integration_test_02.py @@ -12,7 +12,6 @@ # trajectory reperesents a VV firing its adverse thrusters to slow down in preperation for docking. # The test uses JFH data to assert expected strike counts across 15 distinct firings. -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule, TargetVehicle diff --git a/tests/rpod/rpod_integration_test_03.py b/tests/rpod/rpod_integration_test_03.py index 337c149..b1f4ff7 100644 --- a/tests/rpod/rpod_integration_test_03.py +++ b/tests/rpod/rpod_integration_test_03.py @@ -12,7 +12,6 @@ logging.basicConfig(filename='rpod_integration_test_03.log', level=logging.INFO, format='%(message)s') -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule, TargetVehicle diff --git a/tests/rpod/rpod_integration_test_04.py b/tests/rpod/rpod_integration_test_04.py index b8ad47d..cb34183 100644 --- a/tests/rpod/rpod_integration_test_04.py +++ b/tests/rpod/rpod_integration_test_04.py @@ -9,7 +9,6 @@ # ======================== # Test case for producing hollow cube data. -import test_header import unittest, os, sys import pandas as pd diff --git a/tests/rpod/rpod_integration_test_05.py b/tests/rpod/rpod_integration_test_05.py index 53656ab..90ef4d8 100644 --- a/tests/rpod/rpod_integration_test_05.py +++ b/tests/rpod/rpod_integration_test_05.py @@ -8,7 +8,6 @@ # ======================== # Test case for testing plume gas kinetic models in jfh firings **with multiple thrusters per firing**. -import test_header import unittest, os, sys from pyrpod.vehicle import LogisticsModule, TargetVehicle, VisitingVehicle diff --git a/tests/rpod/rpod_unit_test_01.py b/tests/rpod/rpod_unit_test_01.py index 85f924f..5881982 100644 --- a/tests/rpod/rpod_unit_test_01.py +++ b/tests/rpod/rpod_unit_test_01.py @@ -8,7 +8,6 @@ # ======================== # Test case for converting STL data to VTK data. # This is accomplished by checking for the proper data format of VTK files. -import test_header import unittest, os import numpy as np import meshio diff --git a/tests/rpod/rpod_unit_test_02.py b/tests/rpod/rpod_unit_test_02.py index 5338671..65ddfdf 100644 --- a/tests/rpod/rpod_unit_test_02.py +++ b/tests/rpod/rpod_unit_test_02.py @@ -9,7 +9,6 @@ # Test case for reading in JFH data. -import test_header import unittest, os, sys from pyrpod.rpod import JetFiringHistory diff --git a/tests/rpod/rpod_unit_test_03.py b/tests/rpod/rpod_unit_test_03.py index 353e3a8..6206918 100644 --- a/tests/rpod/rpod_unit_test_03.py +++ b/tests/rpod/rpod_unit_test_03.py @@ -10,7 +10,6 @@ # in pyrpod.util.io.file_print without making assertions yet. These files # will serve as fixtures for future tests to lock current behavior. -import test_header # adds project root to sys.path import unittest, os import numpy as np diff --git a/tests/rpod/rpod_verification_test_04.py b/tests/rpod/rpod_verification_test_04.py index 434a5be..7ad3a0a 100644 --- a/tests/rpod/rpod_verification_test_04.py +++ b/tests/rpod/rpod_verification_test_04.py @@ -1,57 +1,56 @@ -# Nicholas A. Palumbo -# University of Central Florida -# Department of Mechanical and Aerospace Engineering -# Last Changed: 3-21-24 - -# ======================== -# PyRPOD: tests/rpod_verification_test_04.py -# ======================== -# Visualizes STLs after decoupling the TCD and verifies that the plume strikes line up with the thrusters. - -import test_header -import unittest, os, sys - -from pyrpod.vehicle import LogisticsModule, TargetVehicle, VisitingVehicle -from pyrpod.rpod import JetFiringHistory, PlumeStrikeEstimationStudy -from pyrpod.mission import MissionEnvironment - -class LoadSTLModels(unittest.TestCase): - def test_decoupled_tcd(self): - - # Set case directory. - case_dir = '../case/tcd_decoupling/' - - # Instantiate JetFiringHistory object. - jfh = JetFiringHistory.JetFiringHistory(case_dir) - jfh.read_jfh() - - # Instantiate TargetVehicle object. - tv = TargetVehicle.TargetVehicle(case_dir) - # Load Target Vehicle. - tv.set_stl() - - # Instantiate VisitingVehicle object. - vv = VisitingVehicle.VisitingVehicle(case_dir) - # Load Visiting Vehicle. - vv.set_stl() - # Load in thruster configuration file. - vv.set_thruster_config() - # Load in cluster configuration file. - vv.set_cluster_config() - # Load in thruster data file - vv.set_thruster_metrics() - - # Set mission environment. - me = MissionEnvironment.MissionEnvironment(case_dir) - - # Instantiate RPOD object. - plume_strike_study = PlumeStrikeEstimationStudy.PlumeStrikeEstimationStudy(me) - # Initiate RPOD study. - plume_strike_study.study_init(jfh, tv, vv) - # Load STLs in Paraview - plume_strike_study.graph_jfh() - # Run plume strike analysis. - plume_strike_study.jfh_plume_strikes() - -if __name__ == '__main__': +# Nicholas A. Palumbo +# University of Central Florida +# Department of Mechanical and Aerospace Engineering +# Last Changed: 3-21-24 + +# ======================== +# PyRPOD: tests/rpod_verification_test_04.py +# ======================== +# Visualizes STLs after decoupling the TCD and verifies that the plume strikes line up with the thrusters. + +import unittest, os, sys + +from pyrpod.vehicle import LogisticsModule, TargetVehicle, VisitingVehicle +from pyrpod.rpod import JetFiringHistory, PlumeStrikeEstimationStudy +from pyrpod.mission import MissionEnvironment + +class LoadSTLModels(unittest.TestCase): + def test_decoupled_tcd(self): + + # Set case directory. + case_dir = '../case/tcd_decoupling/' + + # Instantiate JetFiringHistory object. + jfh = JetFiringHistory.JetFiringHistory(case_dir) + jfh.read_jfh() + + # Instantiate TargetVehicle object. + tv = TargetVehicle.TargetVehicle(case_dir) + # Load Target Vehicle. + tv.set_stl() + + # Instantiate VisitingVehicle object. + vv = VisitingVehicle.VisitingVehicle(case_dir) + # Load Visiting Vehicle. + vv.set_stl() + # Load in thruster configuration file. + vv.set_thruster_config() + # Load in cluster configuration file. + vv.set_cluster_config() + # Load in thruster data file + vv.set_thruster_metrics() + + # Set mission environment. + me = MissionEnvironment.MissionEnvironment(case_dir) + + # Instantiate RPOD object. + plume_strike_study = PlumeStrikeEstimationStudy.PlumeStrikeEstimationStudy(me) + # Initiate RPOD study. + plume_strike_study.study_init(jfh, tv, vv) + # Load STLs in Paraview + plume_strike_study.graph_jfh() + # Run plume strike analysis. + plume_strike_study.jfh_plume_strikes() + +if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/test_all.py b/tests/test_all.py deleted file mode 100644 index bf08fcf..0000000 --- a/tests/test_all.py +++ /dev/null @@ -1,59 +0,0 @@ -# Andy Torres -# University of Central Florida -# Department of Mechanical and Aerospace Engineering -# Last Changed: 12-06-23 - -# ======================== -# PyRPOD: test_all.py -# ======================== -# The script will read and run all test case scripts in numerical order. -# At minimum, this script should be run at the begining and end of each -# day to ensure new development dont break any old code. - -# Good practice calls for re-running this any time code developments need to be tested or debugged. -# Can be multiple times a day or even multiple times per hour depending on the specific task. -# (Re-word to be more clear^?) - -import test_header -import unittest, os, sys -def run_tests(cat, group): - # Run test suite of suplied group. - test_loader = unittest.TestLoader() - cwd = os.getcwd() + '/' + group - pattern = group + '_'+ cat + '_test_*.py' - # print('cwd', cwd) - # print('pattern', pattern) - # input() - test_suite = test_loader.discover(cwd, pattern= pattern) - test_runner = unittest.TextTestRunner(verbosity = 2) - result = test_runner.run(test_suite) - - # Report results, exit if there is an error. - if not result.wasSuccessful(): - sys.exit(1) - - # Return number of tests ran and errors encountered. - result_str = str(result) - tests_run = int(result_str.split()[1][-1]) - errors = int(result_str.split()[2][-1]) - # errors = int(result_str) - return tests_run, errors - -if __name__ == '__main__': - - test_cats = ['unit', 'integration', 'verification'] - test_groups = ['plume', 'rpod', 'mission', 'mdao'] - - # Run all tests in their groups. - total_tests = 0 - total_errors = 0 - for group in test_groups: - for cat in test_cats: - tests_run, errors = run_tests(cat, group) - total_tests += tests_run - total_errors += errors - - # Report cummulative results. - with open('results.txt', 'a') as f: - message = str(total_tests) + " tests ran, " + str(total_errors) + " total errors\n" - f.write(message) diff --git a/tests/test_header.py b/tests/test_header.py deleted file mode 100644 index 3923546..0000000 --- a/tests/test_header.py +++ /dev/null @@ -1,14 +0,0 @@ -# Andy Torres -# University of Central Florida -# Department of Mechanical and Aerospace Engineering -# Last Changed: 12-06-23 - -# ======================== -# PyRPOD: test/test_header.py -# ======================== -# Pretty janky, but importing this header file into your working directory -# is a simple way to allow the source code to be run outisde of it's own directory. - -import sys - -sys.path.insert(0, '../') \ No newline at end of file