From 62b02006ffa1c12a822ba94abc3133641b3c1239 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 10 Sep 2024 14:21:36 +0200 Subject: [PATCH 01/34] rename rae2a to rad2a --- posydon/utils/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posydon/utils/constants.py b/posydon/utils/constants.py index 66d8038d8..1da3b21f8 100644 --- a/posydon/utils/constants.py +++ b/posydon/utils/constants.py @@ -19,7 +19,7 @@ pi = 3.1415926535897932384626433832795028841971693993751 a2rad = pi / 180.0 # angle to radians -rae2a = 180.0 / pi # radians to angle +rad2a = 180.0 / pi # radians to angle # PHYSICAL CONSTANTS From e9e9a874e287c1825223e0fd05f5032229ebe1ac Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 10 Sep 2024 14:22:47 +0200 Subject: [PATCH 02/34] add unit_tests: readme, test_constants.py, test_limits_thresholds.py --- posydon/unit_tests/README.md | 11 + posydon/unit_tests/utils/test_constants.py | 491 ++++++++++++++++++ .../utils/test_limits_thresholds.py | 122 +++++ 3 files changed, 624 insertions(+) create mode 100644 posydon/unit_tests/README.md create mode 100644 posydon/unit_tests/utils/test_constants.py create mode 100644 posydon/unit_tests/utils/test_limits_thresholds.py diff --git a/posydon/unit_tests/README.md b/posydon/unit_tests/README.md new file mode 100644 index 000000000..2a582cd98 --- /dev/null +++ b/posydon/unit_tests/README.md @@ -0,0 +1,11 @@ +# POSYDON: unit tests + +Here we collect our unit tests for the POSYDON code. We are using the [python `unittest` module](https://docs.python.org/3/library/unittest.html). + +The tests in each `TESTFILE` can be run via + + python -m unittest TESTFILE + +If you like to run all unit tests you can use + + python -m unittest $PATH_TO_POSYDON/posydon/unit_tests/*/*.py \ No newline at end of file diff --git a/posydon/unit_tests/utils/test_constants.py b/posydon/unit_tests/utils/test_constants.py new file mode 100644 index 000000000..7c0b76af6 --- /dev/null +++ b/posydon/unit_tests/utils/test_constants.py @@ -0,0 +1,491 @@ +"""Unit tests of posydon/utils/constants.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the unittest module and the module which will be tested +import unittest +import posydon.utils.constants as totest + +# import other needed code for the tests +# not needed + +# define test classes +class TestElements(unittest.TestCase): + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['H_weight', 'He_weight', 'Lsun', 'Lsun33', 'Msun', + 'Msun33', 'Qconv', 'Rsun', 'Rsun11', 'SNcheck_ERR', + 'Teffsol', 'Zsun', '__authors__', '__builtins__', + '__cached__', '__doc__', '__file__', '__loader__', + '__name__', '__package__', '__spec__', 'a2rad', + 'age_of_universe', 'agesol', 'amu', 'asol', 'au', 'aursun', + 'avo', 'boltz_sigma', 'boltzm', 'cgas', 'clight', 'crad', + 'day2sec', 'dayyer', 'ev2erg', 'fine', 'hbar', 'hion', + 'inversecm2erg', 'kerg', 'kev', 'km2cm', 'loggsol', 'lsol', + 'ly', 'm_earth', 'm_jupiter', 'mbolsol', 'mbolsun', 'me', + 'mev_amu', 'mev_to_ergs', 'mn', 'mp', 'msol', 'pc', 'pi', + 'planck_h', 'qe', 'r_earth', 'r_jupiter', 'rad2a', 'rbohr', + 'rhonuc', 'rsol', 'secyer', 'semimajor_axis_jupiter', + 'ssol', 'standard_cgrav', 'weinfre', 'weinlam'] + self.assertListEqual(dir(totest), elements, + msg="There might be added or removed objects " + "without an update on the unit test.") + + def test_instance_pi(self): + self.assertIsInstance(totest.pi, (float, int)) + + def test_instance_a2rad(self): + self.assertIsInstance(totest.a2rad, (float, int)) + + def test_instance_rad2a(self): + self.assertIsInstance(totest.rad2a, (float, int)) + + def test_instance_standard_cgrav(self): + self.assertIsInstance(totest.standard_cgrav, (float, int)) + + def test_instance_planck_h(self): + self.assertIsInstance(totest.planck_h, (float, int)) + + def test_instance_hbar(self): + self.assertIsInstance(totest.hbar, (float, int)) + + def test_instance_qe(self): + self.assertIsInstance(totest.qe, (float, int)) + + def test_instance_avo(self): + self.assertIsInstance(totest.avo, (float, int)) + + def test_instance_clight(self): + self.assertIsInstance(totest.clight, (float, int)) + + def test_instance_kerg(self): + self.assertIsInstance(totest.kerg, (float, int)) + + def test_instance_boltzm(self): + self.assertIsInstance(totest.boltzm, (float, int)) + + def test_instance_cgas(self): + self.assertIsInstance(totest.cgas, (float, int)) + + def test_instance_kev(self): + self.assertIsInstance(totest.kev, (float, int)) + + def test_instance_amu(self): + self.assertIsInstance(totest.amu, (float, int)) + + def test_instance_mn(self): + self.assertIsInstance(totest.mn, (float, int)) + + def test_instance_mp(self): + self.assertIsInstance(totest.mp, (float, int)) + + def test_instance_me(self): + self.assertIsInstance(totest.me, (float, int)) + + def test_instance_rbohr(self): + self.assertIsInstance(totest.rbohr, (float, int)) + + def test_instance_fine(self): + self.assertIsInstance(totest.fine, (float, int)) + + def test_instance_hion(self): + self.assertIsInstance(totest.hion, (float, int)) + + def test_instance_ev2erg(self): + self.assertIsInstance(totest.ev2erg, (float, int)) + + def test_instance_inversecm2erg(self): + self.assertIsInstance(totest.inversecm2erg, (float, int)) + + def test_instance_mev_to_ergs(self): + self.assertIsInstance(totest.mev_to_ergs, (float, int)) + + def test_instance_mev_amu(self): + self.assertIsInstance(totest.mev_amu, (float, int)) + + def test_instance_Qconv(self): + self.assertIsInstance(totest.Qconv, (float, int)) + + def test_instance_boltz_sigma(self): + self.assertIsInstance(totest.boltz_sigma, (float, int)) + + def test_instance_crad(self): + self.assertIsInstance(totest.crad, (float, int)) + + def test_instance_ssol(self): + self.assertIsInstance(totest.ssol, (float, int)) + + def test_instance_asol(self): + self.assertIsInstance(totest.asol, (float, int)) + + def test_instance_weinlam(self): + self.assertIsInstance(totest.weinlam, (float, int)) + + def test_instance_weinfre(self): + self.assertIsInstance(totest.weinfre, (float, int)) + + def test_instance_rhonuc(self): + self.assertIsInstance(totest.rhonuc, (float, int)) + + def test_instance_Zsun(self): + self.assertIsInstance(totest.Zsun, (float, int)) + + def test_instance_msol(self): + self.assertIsInstance(totest.msol, (float, int)) + + def test_instance_rsol(self): + self.assertIsInstance(totest.rsol, (float, int)) + + def test_instance_lsol(self): + self.assertIsInstance(totest.lsol, (float, int)) + + def test_instance_agesol(self): + self.assertIsInstance(totest.agesol, (float, int)) + + def test_instance_Msun(self): + self.assertIsInstance(totest.Msun, (float, int)) + + def test_instance_Rsun(self): + self.assertIsInstance(totest.Rsun, (float, int)) + + def test_instance_Lsun(self): + self.assertIsInstance(totest.Lsun, (float, int)) + + def test_instance_Msun33(self): + self.assertIsInstance(totest.Msun33, (float, int)) + + def test_instance_Rsun11(self): + self.assertIsInstance(totest.Rsun11, (float, int)) + + def test_instance_Lsun33(self): + self.assertIsInstance(totest.Lsun33, (float, int)) + + def test_instance_ly(self): + self.assertIsInstance(totest.ly, (float, int)) + + def test_instance_pc(self): + self.assertIsInstance(totest.pc, (float, int)) + + def test_instance_secyer(self): + self.assertIsInstance(totest.secyer, (float, int)) + + def test_instance_dayyer(self): + self.assertIsInstance(totest.dayyer, (float, int)) + + def test_instance_age_of_universe(self): + self.assertIsInstance(totest.age_of_universe, (float, int)) + + def test_instance_Teffsol(self): + self.assertIsInstance(totest.Teffsol, (float, int)) + + def test_instance_loggsol(self): + self.assertIsInstance(totest.loggsol, (float, int)) + + def test_instance_mbolsun(self): + self.assertIsInstance(totest.mbolsun, (float, int)) + + def test_instance_mbolsol(self): + self.assertIsInstance(totest.mbolsol, (float, int)) + + def test_instance_m_earth(self): + self.assertIsInstance(totest.m_earth, (float, int)) + + def test_instance_r_earth(self): + self.assertIsInstance(totest.r_earth, (float, int)) + + def test_instance_au(self): + self.assertIsInstance(totest.au, (float, int)) + + def test_instance_aursun(self): + self.assertIsInstance(totest.aursun, (float, int)) + + def test_instance_m_jupiter(self): + self.assertIsInstance(totest.m_jupiter, (float, int)) + + def test_instance_r_jupiter(self): + self.assertIsInstance(totest.r_jupiter, (float, int)) + + def test_instance_semimajor_axis_jupiter(self): + self.assertIsInstance(totest.semimajor_axis_jupiter, (float, int)) + + def test_instance_km2cm(self): + self.assertIsInstance(totest.km2cm, (float, int)) + + def test_instance_day2sec(self): + self.assertIsInstance(totest.day2sec, (float, int)) + + def test_instance_H_weight(self): + self.assertIsInstance(totest.H_weight, (float, int)) + + def test_instance_He_weight(self): + self.assertIsInstance(totest.He_weight, (float, int)) + + def test_instance_SNcheck_ERR(self): + self.assertIsInstance(totest.SNcheck_ERR, (float, int)) + + +class TestValues(unittest.TestCase): + # check that the values fit + # use delta of last digit times 0.6 + def test_value_pi(self): + value = 3.1415926535897932384626433832795028841971693993751 + self.assertAlmostEqual(totest.pi, value, delta=6e-50) + + def test_value_a2rad(self): + value = 1.7453292519943295e-2 + self.assertAlmostEqual(totest.a2rad, value, delta=6e-19) + + def test_value_rad2a(self): + value = 5.729577951308232e+1 + self.assertAlmostEqual(totest.rad2a, value, delta=6e-15) + + def test_value_standard_cgrav(self): + value = 6.67428e-8 + self.assertAlmostEqual(totest.standard_cgrav, value, delta=6e-14) + + def test_value_planck_h(self): + value = 6.62606896e-27 + self.assertAlmostEqual(totest.planck_h, value, delta=6e-36) + + def test_value_hbar(self): + value = 1.05457163e-27 + self.assertAlmostEqual(totest.hbar, value, delta=6e-36) + + def test_value_qe(self): + value = 4.80320440e-10 + self.assertAlmostEqual(totest.qe, value, delta=6e-19) + + def test_value_avo(self): + value = 6.02214129e+23 + self.assertAlmostEqual(totest.avo, value, delta=6e+14) + + def test_value_clight(self): + value = 2.99792458e+10 + self.assertAlmostEqual(totest.clight, value, delta=6e+1) + + def test_value_kerg(self): + value = 1.3806504e-16 + self.assertAlmostEqual(totest.kerg, value, delta=6e-24) + + def test_value_boltzm(self): + value = 1.3806504e-16 + self.assertAlmostEqual(totest.kerg, value, delta=6e-24) + + def test_value_cgas(self): + value = 8.314471780895016e+7 + self.assertAlmostEqual(totest.cgas, value, delta=6e-1) + + def test_value_kev(self): + value = 8.617385e-5 + self.assertAlmostEqual(totest.kev, value, delta=6e-12) + + def test_value_amu(self): + value = 1.6605389210321898e-24 + self.assertAlmostEqual(totest.amu, value, delta=6e-33) + + def test_value_mn(self): + value = 1.6749286e-24 + self.assertAlmostEqual(totest.mn, value, delta=6e-32) + + def test_value_mp(self): + value = 1.6726231e-24 + self.assertAlmostEqual(totest.mp, value, delta=6e-32) + + def test_value_me(self): + value = 9.1093826e-28 + self.assertAlmostEqual(totest.me, value, delta=6e-36) + + def test_value_rbohr(self): + value = 5.291771539809704e-9 + self.assertAlmostEqual(totest.rbohr, value, delta=6e-18) + + def test_value_fine(self): + value = 7.297352926107705e-3 + self.assertAlmostEqual(totest.fine, value, delta=6e-11) + + def test_value_hion(self): + value = 1.3605698140e+1 + self.assertAlmostEqual(totest.hion, value, delta=6e-10) + + def test_value_ev2erg(self): + value = 1.602176565e-12 + self.assertAlmostEqual(totest.ev2erg, value, delta=6e-22) + + def test_value_inversecm2erg(self): + value = 1.9864455003959037e-16 + self.assertAlmostEqual(totest.inversecm2erg, value, delta=6e-25) + + def test_value_mev_to_ergs(self): + value = 1.6021765649999999e-6 + self.assertAlmostEqual(totest.mev_to_ergs, value, delta=6e-16) + + def test_value_mev_amu(self): + value = 9.648533645956869e+17 + self.assertAlmostEqual(totest.mev_amu, value, delta=6e+8) + + def test_value_Qconv(self): + value = 9.648533645956868e+17 + self.assertAlmostEqual(totest.Qconv, value, delta=6e+8) + + def test_value_boltz_sigma(self): + value = 5.670373e-5 + self.assertAlmostEqual(totest.boltz_sigma, value, delta=6e-12) + + def test_value_crad(self): + value = 7.565731356724124e-15 + self.assertAlmostEqual(totest.crad, value, delta=6e-22) + + def test_value_ssol(self): + value = 5.670373e-5 + self.assertAlmostEqual(totest.ssol, value, delta=6e-12) + + def test_value_asol(self): + value = 7.565731356724124e-15 + self.assertAlmostEqual(totest.asol, value, delta=6e-22) + + def test_value_weinlam(self): + value = 2.897768496231288e-1 + self.assertAlmostEqual(totest.weinlam, value, delta=6e-9) + + def test_value_weinfre(self): + value = 5.878932774535368e+10 + self.assertAlmostEqual(totest.weinfre, value, delta=6e+2) + + def test_value_rhonuc(self): + value = 2.342e+14 + self.assertAlmostEqual(totest.rhonuc, value, delta=6e+10) + + def test_value_Zsun(self): + value = 1.42e-2 + self.assertAlmostEqual(totest.Zsun, value, delta=6e-5) + + def test_value_msol(self): + value = 1.9892e+33 + self.assertAlmostEqual(totest.msol, value, delta=6e+28) + + def test_value_rsol(self): + value = 6.9598e+10 + self.assertAlmostEqual(totest.rsol, value, delta=6e+5) + + def test_value_lsol(self): + value = 3.8418e+33 + self.assertAlmostEqual(totest.lsol, value, delta=6e+28) + + def test_value_agesol(self): + value = 4.57e+9 + self.assertAlmostEqual(totest.agesol, value, delta=6e+6) + + def test_value_Msun(self): + value = 1.9892e+33 + self.assertAlmostEqual(totest.Msun, value, delta=6e+28) + + def test_value_Rsun(self): + value = 6.9598e+10 + self.assertAlmostEqual(totest.Rsun, value, delta=6e+5) + + def test_value_Lsun(self): + value = 3.8418e+33 + self.assertAlmostEqual(totest.Lsun, value, delta=6e+28) + + def test_value_Msun33(self): + value = 1.9892 + self.assertAlmostEqual(totest.Msun33, value, delta=6e-5) + + def test_value_Rsun11(self): + value = 6.9598e-1 + self.assertAlmostEqual(totest.Rsun11, value, delta=6e-6) + + def test_value_Lsun33(self): + value = 3.8418 + self.assertAlmostEqual(totest.Lsun33, value, delta=6e-5) + + def test_value_ly(self): + value = 9.460528e+17 + self.assertAlmostEqual(totest.ly, value, delta=6e+10) + + def test_value_pc(self): + value = 3.0856770322224e+18 + self.assertAlmostEqual(totest.pc, value, delta=6e+11) + + def test_value_secyer(self): + value = 3.1558149984e+7 + self.assertAlmostEqual(totest.secyer, value, delta=6e-4) + + def test_value_dayyer(self): + value = 3.6525e+2 + self.assertAlmostEqual(totest.dayyer, value, delta=6e-3) + + def test_value_age_of_universe(self): + value = 1.38e+10 + self.assertAlmostEqual(totest.age_of_universe, value, delta=6e+7) + + def test_value_Teffsol(self): + value = 5.7770e+3 + self.assertAlmostEqual(totest.Teffsol, value, delta=6e-2) + + def test_value_loggsol(self): + value = 4.4378893534131256 + self.assertAlmostEqual(totest.loggsol, value, delta=6e-17) + + def test_value_mbolsun(self): + value = 4.746 + self.assertAlmostEqual(totest.mbolsun, value, delta=6e-4) + + def test_value_mbolsol(self): + value = 4.746 + self.assertAlmostEqual(totest.mbolsol, value, delta=6e-4) + + def test_value_m_earth(self): + value = 5.9764e+27 + self.assertAlmostEqual(totest.m_earth, value, delta=6e+22) + + def test_value_r_earth(self): + value = 6.37e+8 + self.assertAlmostEqual(totest.r_earth, value, delta=6e+5) + + def test_value_au(self): + value = 1.495978921e+13 + self.assertAlmostEqual(totest.au, value, delta=6e+3) + + def test_value_aursun(self): + value = 2.1495e+2 + self.assertAlmostEqual(totest.aursun, value, delta=6e-3) + + def test_value_m_jupiter(self): + value = 1.8986e+30 + self.assertAlmostEqual(totest.m_jupiter, value, delta=6e+25) + + def test_value_r_jupiter(self): + value = 6.9911e+9 + self.assertAlmostEqual(totest.r_jupiter, value, delta=6e+4) + + def test_value_semimajor_axis_jupiter(self): + value = 7.7857e+13 + self.assertAlmostEqual(totest.semimajor_axis_jupiter, value, delta=6e+8) + + def test_value_km2cm(self): + value = 1.0e5 + self.assertAlmostEqual(totest.km2cm, value, delta=6e-16) + + def test_value_day2sec(self): + value = 8.64000e+4 + self.assertAlmostEqual(totest.day2sec, value, delta=6e-2) + + def test_value_H_weight(self): + value = 1.00782503207 + self.assertAlmostEqual(totest.H_weight, value, delta=6e-12) + + def test_value_He_weight(self): + value = 4.002603254131 + self.assertAlmostEqual(totest.He_weight, value, delta=6e-13) + + def test_value_SNcheck_ERR(self): + value = 1e-10 + self.assertAlmostEqual(totest.SNcheck_ERR, value, delta=6e-25) + + +if __name__ == "__main__": + unittest.main() diff --git a/posydon/unit_tests/utils/test_limits_thresholds.py b/posydon/unit_tests/utils/test_limits_thresholds.py new file mode 100644 index 000000000..b32785d10 --- /dev/null +++ b/posydon/unit_tests/utils/test_limits_thresholds.py @@ -0,0 +1,122 @@ +"""Unit tests of posydon/utils/limits_thresholds.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the unittest module and the module which will be tested +import unittest +import posydon.utils.limits_thresholds as totest + +# import other needed code for the tests +# not needed + +# define test classes +class TestElements(unittest.TestCase): + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['LG_MTRANSFER_RATE_THRESHOLD', 'LOG10_BURNING_THRESHOLD', + 'NEUTRINO_MASS_LOSS_UPPER_LIMIT', + 'REL_LOG10_BURNING_THRESHOLD', + 'RL_RELATIVE_OVERFLOW_THRESHOLD', + 'STATE_NS_STARMASS_UPPER_LIMIT', + 'THRESHOLD_CENTRAL_ABUNDANCE', + 'THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C', + 'THRESHOLD_HE_NAKED_ABUNDANCE', + 'THRESHOLD_NUCLEAR_LUMINOSITY', '__authors__', + '__builtins__', '__cached__', '__doc__', '__file__', + '__loader__', '__name__', '__package__', '__spec__', 'np'] + self.assertListEqual(dir(totest), elements, + msg="There might be added or removed objects " + "without an update on the unit test.") + + def test_instance_RL_RELATIVE_OVERFLOW_THRESHOLD(self): + self.assertIsInstance(totest.RL_RELATIVE_OVERFLOW_THRESHOLD, + (float, int)) + + def test_instance_LG_MTRANSFER_RATE_THRESHOLD(self): + self.assertIsInstance(totest.LG_MTRANSFER_RATE_THRESHOLD, + (float, int)) + + def test_instance_THRESHOLD_CENTRAL_ABUNDANCE(self): + self.assertIsInstance(totest.THRESHOLD_CENTRAL_ABUNDANCE, + (float, int)) + + def test_instance_THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C(self): + self.assertIsInstance(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C, + (float, int)) + + def test_instance_THRESHOLD_HE_NAKED_ABUNDANCE(self): + self.assertIsInstance(totest.THRESHOLD_HE_NAKED_ABUNDANCE, + (float, int)) + + def test_instance_THRESHOLD_NUCLEAR_LUMINOSITY(self): + self.assertIsInstance(totest.THRESHOLD_NUCLEAR_LUMINOSITY, + (float, int)) + + def test_instance_REL_LOG10_BURNING_THRESHOLD(self): + self.assertIsInstance(totest.REL_LOG10_BURNING_THRESHOLD, + (float, int)) + + def test_instance_LOG10_BURNING_THRESHOLD(self): + self.assertIsInstance(totest.LOG10_BURNING_THRESHOLD, + (float, int)) + + def test_instance_STATE_NS_STARMASS_UPPER_LIMIT(self): + self.assertIsInstance(totest.STATE_NS_STARMASS_UPPER_LIMIT, + (float, int)) + + def test_instance_NEUTRINO_MASS_LOSS_UPPER_LIMIT(self): + self.assertIsInstance(totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT, + (float, int)) + + +class TestLimits(unittest.TestCase): + # check for validity ranges +# def test_limits_RL_RELATIVE_OVERFLOW_THRESHOLD(self): + # has no limits + +# def test_limits_LG_MTRANSFER_RATE_THRESHOLD(self): + # has no limits + + def test_limits_THRESHOLD_CENTRAL_ABUNDANCE(self): + # an abundance should be in [0,1] + self.assertGreaterEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE, 0.0) + self.assertLessEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE, 1.0) + + def test_limits_THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C(self): + # an abundance should be in [0,1] + self.assertGreaterEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C, 0.0) + self.assertLessEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C, 1.0) + # it should be limited by THRESHOLD_CENTRAL_ABUNDANCE + self.assertGreaterEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C, totest.THRESHOLD_CENTRAL_ABUNDANCE) + + def test_limits_THRESHOLD_HE_NAKED_ABUNDANCE(self): + # an abundance should be in [0,1] + self.assertGreaterEqual(totest.THRESHOLD_HE_NAKED_ABUNDANCE, 0.0) + self.assertLessEqual(totest.THRESHOLD_HE_NAKED_ABUNDANCE, 1.0) + + def test_limits_THRESHOLD_NUCLEAR_LUMINOSITY(self): + # an fraction should be in [0,1] + self.assertGreaterEqual(totest.THRESHOLD_NUCLEAR_LUMINOSITY, 0.0) + self.assertLessEqual(totest.THRESHOLD_NUCLEAR_LUMINOSITY, 1.0) + + def test_limits_REL_LOG10_BURNING_THRESHOLD(self): + # the log of a fraction should be <0 + self.assertLessEqual(totest.REL_LOG10_BURNING_THRESHOLD, 0.0) + +# def test_limits_LOG10_BURNING_THRESHOLD(self): + # has no limits + + def test_limits_STATE_NS_STARMASS_UPPER_LIMIT(self): + # a mass should be >0 + self.assertGreater(totest.STATE_NS_STARMASS_UPPER_LIMIT, 0.0) + + def test_limits_NEUTRINO_MASS_LOSS_UPPER_LIMIT(self): + # a mass limit should be >=0 + self.assertGreaterEqual(totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT, 0.0) + + +if __name__ == "__main__": + unittest.main() From 346bf0d02c5acb313f735af32289cddaa309374a Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 10 Sep 2024 14:37:41 +0200 Subject: [PATCH 03/34] add template --- posydon/unit_tests/README.md | 2 +- posydon/unit_tests/test_template.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 posydon/unit_tests/test_template.py diff --git a/posydon/unit_tests/README.md b/posydon/unit_tests/README.md index 2a582cd98..a469ca052 100644 --- a/posydon/unit_tests/README.md +++ b/posydon/unit_tests/README.md @@ -4,7 +4,7 @@ Here we collect our unit tests for the POSYDON code. We are using the [python `u The tests in each `TESTFILE` can be run via - python -m unittest TESTFILE + python -m unittest TESTFILE If you like to run all unit tests you can use diff --git a/posydon/unit_tests/test_template.py b/posydon/unit_tests/test_template.py new file mode 100644 index 000000000..b3df0a59f --- /dev/null +++ b/posydon/unit_tests/test_template.py @@ -0,0 +1,22 @@ +"""Unit tests of posydon/user_modules/my_flow_chart_example.py +""" + +__authors__ = [ + "John Doe " +] + +# import the unittest module and the module which will be tested +import unittest +import posydon.user_modules.my_flow_chart_example as totest + +# import other needed code for the tests +# not needed + +# define test classes +class TestCode(unittest.TestCase): + def test_name(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() From d369e39bcfdc137d8ef987f656174e07ff2f8530 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 10 Sep 2024 16:49:09 +0200 Subject: [PATCH 04/34] add authors to ignorereason.py --- posydon/utils/ignorereason.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/posydon/utils/ignorereason.py b/posydon/utils/ignorereason.py index a1fc3f9d0..30cfadf0b 100644 --- a/posydon/utils/ignorereason.py +++ b/posydon/utils/ignorereason.py @@ -1,6 +1,12 @@ """Class for handling ignore reasons when creating new grids.""" +__authors__ = [ + "Konstantinos Kovlakas ", + "Matthias Kruckow ", +] + + IGNORE_REASONS_PRIORITY = [ # missing data files 'ignored_no_history1', # history1 is always needed From 7fc0f269ec16eab0d7527f789548772351b8b896 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 10 Sep 2024 16:49:45 +0200 Subject: [PATCH 05/34] add test_ignorereason.py --- posydon/unit_tests/utils/test_ignorereason.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 posydon/unit_tests/utils/test_ignorereason.py diff --git a/posydon/unit_tests/utils/test_ignorereason.py b/posydon/unit_tests/utils/test_ignorereason.py new file mode 100644 index 000000000..c546d480f --- /dev/null +++ b/posydon/unit_tests/utils/test_ignorereason.py @@ -0,0 +1,92 @@ +"""Unit tests of posydon/utils/ignorereason.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the unittest module and the module which will be tested +import unittest +import posydon.utils.ignorereason as totest + +# import other needed code for the tests +# not needed + +# define test classes +class TestElements(unittest.TestCase): + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['IGNORE_REASONS_PRIORITY', 'IgnoreReason', '__authors__', + '__builtins__', '__cached__', '__doc__', '__file__', + '__loader__', '__name__', '__package__', '__spec__'] + self.assertListEqual(dir(totest), elements, + msg="There might be added or removed objects " + "without an update on the unit test.") + + def test_instance_IGNORE_REASONS_PRIORITY(self): + self.assertIsInstance(totest.IGNORE_REASONS_PRIORITY, + (list)) + + +class TestValues(unittest.TestCase): + # check that the values fit + def test_value_IGNORE_REASONS_PRIORITY(self): + self.assertIn('ignored_no_history1', totest.IGNORE_REASONS_PRIORITY) + self.assertIn('ignored_no_binary_history', + totest.IGNORE_REASONS_PRIORITY) + self.assertIn('corrupted_history1', totest.IGNORE_REASONS_PRIORITY) + self.assertIn('corrupted_binary_history', + totest.IGNORE_REASONS_PRIORITY) + self.assertIn('corrupted_history2', totest.IGNORE_REASONS_PRIORITY) + self.assertIn('ignored_scrubbed_history', + totest.IGNORE_REASONS_PRIORITY) + self.assertIn('ignored_no_final_profile', + totest.IGNORE_REASONS_PRIORITY) + self.assertIn('ignored_no_RLO', totest.IGNORE_REASONS_PRIORITY) + + +class TestIgnoreReason(unittest.TestCase): + # test the IgnoreReason class + def setUp(self): + # initialize an instance of the class for each test + self.IgnoreReason = totest.IgnoreReason() + + def test_init(self): + # check that the instance is of correct type and all code in the + # __init__ got exequted: the elements are created and initialized + self.assertIsInstance(self.IgnoreReason, totest.IgnoreReason) + self.assertIsNone(self.IgnoreReason.reason) + self.assertIsNone(self.IgnoreReason.order) + + def test_bool(self): + self.assertFalse(self.IgnoreReason) + self.IgnoreReason.reason = totest.IGNORE_REASONS_PRIORITY[0] + self.assertTrue(self.IgnoreReason) + + def test_setattr(self): + # try to set order: shouldn't change anything + self.IgnoreReason.order = 0 + self.assertIsNone(self.IgnoreReason.reason) + self.assertIsNone(self.IgnoreReason.order) + # set all reasons in decreasing order and compare it + for r in reversed(totest.IGNORE_REASONS_PRIORITY): + o = totest.IGNORE_REASONS_PRIORITY.index(r) + self.IgnoreReason.reason = r + self.assertEqual(self.IgnoreReason.order, o) + self.assertEqual(self.IgnoreReason.reason, r) + # try to set reason of lowest priority: reason of higher pririty will + # be kept + self.IgnoreReason.reason = totest.IGNORE_REASONS_PRIORITY[-1] + self.assertEqual(self.IgnoreReason.order, o) + self.assertEqual(self.IgnoreReason.reason, r) + # unset the reason: set back to None + self.IgnoreReason.reason = None + self.assertIsNone(self.IgnoreReason.reason) + self.assertIsNone(self.IgnoreReason.order) + # try error on non existing reason + with self.assertRaises(ValueError): + self.IgnoreReason.reason = '' + + +if __name__ == "__main__": + unittest.main() From 28c2d60e87e9a0edc0e007378c6b3f768db2c579 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Wed, 11 Sep 2024 10:49:59 +0200 Subject: [PATCH 06/34] add test_posydonerror.py --- posydon/unit_tests/utils/test_ignorereason.py | 11 +- posydon/unit_tests/utils/test_posydonerror.py | 134 ++++++++++++++++++ posydon/utils/posydonerror.py | 29 +++- 3 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 posydon/unit_tests/utils/test_posydonerror.py diff --git a/posydon/unit_tests/utils/test_ignorereason.py b/posydon/unit_tests/utils/test_ignorereason.py index c546d480f..ae114ae05 100644 --- a/posydon/unit_tests/utils/test_ignorereason.py +++ b/posydon/unit_tests/utils/test_ignorereason.py @@ -10,7 +10,7 @@ import posydon.utils.ignorereason as totest # import other needed code for the tests -# not needed +from inspect import isclass, isroutine # define test classes class TestElements(unittest.TestCase): @@ -27,6 +27,9 @@ def test_instance_IGNORE_REASONS_PRIORITY(self): self.assertIsInstance(totest.IGNORE_REASONS_PRIORITY, (list)) + def test_instance_IgnoreReason(self): + self.assertTrue(isclass(totest.IgnoreReason)) + class TestValues(unittest.TestCase): # check that the values fit @@ -52,18 +55,21 @@ def setUp(self): self.IgnoreReason = totest.IgnoreReason() def test_init(self): + self.assertTrue(isroutine(self.IgnoreReason.__init__)) # check that the instance is of correct type and all code in the - # __init__ got exequted: the elements are created and initialized + # __init__ got executed: the elements are created and initialized self.assertIsInstance(self.IgnoreReason, totest.IgnoreReason) self.assertIsNone(self.IgnoreReason.reason) self.assertIsNone(self.IgnoreReason.order) def test_bool(self): + self.assertTrue(isroutine(self.IgnoreReason.__bool__)) self.assertFalse(self.IgnoreReason) self.IgnoreReason.reason = totest.IGNORE_REASONS_PRIORITY[0] self.assertTrue(self.IgnoreReason) def test_setattr(self): + self.assertTrue(isroutine(self.IgnoreReason.__setattr__)) # try to set order: shouldn't change anything self.IgnoreReason.order = 0 self.assertIsNone(self.IgnoreReason.reason) @@ -84,6 +90,7 @@ def test_setattr(self): self.assertIsNone(self.IgnoreReason.reason) self.assertIsNone(self.IgnoreReason.order) # try error on non existing reason + self.assertNotIn('', totest.IGNORE_REASONS_PRIORITY) with self.assertRaises(ValueError): self.IgnoreReason.reason = '' diff --git a/posydon/unit_tests/utils/test_posydonerror.py b/posydon/unit_tests/utils/test_posydonerror.py new file mode 100644 index 000000000..ec18f2601 --- /dev/null +++ b/posydon/unit_tests/utils/test_posydonerror.py @@ -0,0 +1,134 @@ +"""Unit tests of posydon/utils/posydonerror.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the unittest module and the module which will be tested +import unittest +import posydon.utils.posydonerror as totest + +# import other needed code for the tests +from inspect import isclass, isroutine + +# define test classes +class TestElements(unittest.TestCase): + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['BinaryStar', 'FlowError', 'GridError', 'MatchingError', + 'ModelError', 'NumericalError', 'POSYDONError', + 'SingleStar', '__authors__', '__builtins__', '__cached__', + '__doc__', '__file__', '__loader__', '__name__', + '__package__', '__spec__', 'copy', + 'initial_condition_message'] + self.assertListEqual(dir(totest), elements, + msg="There might be added or removed objects " + "without an update on the unit test.") + + def test_instance_POSYDONError(self): + self.assertTrue(isclass(totest.POSYDONError)) + self.assertTrue(issubclass(totest.POSYDONError, Exception)) + + def test_instance_FlowError(self): + self.assertTrue(isclass(totest.FlowError)) + self.assertTrue(issubclass(totest.FlowError, totest.POSYDONError)) + + def test_instance_GridError(self): + self.assertTrue(isclass(totest.GridError)) + self.assertTrue(issubclass(totest.GridError, totest.POSYDONError)) + + def test_instance_MatchingError(self): + self.assertTrue(isclass(totest.MatchingError)) + self.assertTrue(issubclass(totest.MatchingError, totest.POSYDONError)) + + def test_instance_ModelError(self): + self.assertTrue(isclass(totest.ModelError)) + self.assertTrue(issubclass(totest.ModelError, totest.POSYDONError)) + + def test_instance_NumericalError(self): + self.assertTrue(isclass(totest.NumericalError)) + self.assertTrue(issubclass(totest.NumericalError, totest.POSYDONError)) + + def test_initial_condition_message(self): + self.assertTrue(isroutine(totest.initial_condition_message)) + + +class TestFunctions(unittest.TestCase): + # test functions + def setUp(self): + # initialize a BinaryStar instance, which is a required argument + self.BinaryStar = totest.BinaryStar() + + def test_initial_condition_message(self): + test_object = {'Test': 'object'} + with self.assertRaises(TypeError): + message = totest.initial_condition_message(binary=test_object) + self.assertIn("Failed Binary Initial Conditions", + totest.initial_condition_message(binary=self.BinaryStar)) + with self.assertRaises(TypeError): + message = totest.initial_condition_message(binary=test_object, + ini_params=1) + with self.assertRaises(TypeError): + message = totest.initial_condition_message(binary=test_object, + ini_params=[1,2]) + self.assertEqual("a: 1\nb: 2\n", + totest.initial_condition_message(binary=self.BinaryStar, + ini_params=["a: 1\n", "b: 2\n"])) + + +class TestPOSYDONError(unittest.TestCase): + # test the POSYDONError class + def setUp(self): + # initialize an instance of the class for each test + self.POSYDONError = totest.POSYDONError("test message on posittion") + + def test_init(self): + self.assertTrue(isroutine(self.POSYDONError.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.POSYDONError, totest.POSYDONError) + self.assertEqual(self.POSYDONError.message, + "test message on posittion") + self.assertIsNone(self.POSYDONError.objects) + # check defaults + error_object = totest.POSYDONError() + self.assertEqual(error_object.message, "") + self.assertIsNone(error_object.objects) + # test a passed message + error_object = totest.POSYDONError(message="test message with key") + self.assertEqual(error_object.message, "test message with key") + self.assertIsNone(error_object.objects) + # test a passed object + test_object = {'Test': 'object'} + error_object = totest.POSYDONError(objects=[test_object]) + self.assertEqual(error_object.message, "") + self.assertListEqual(error_object.objects, [test_object]) + # test requests on input parameters + with self.assertRaises(TypeError): + error_object = totest.POSYDONError(message=test_object) + with self.assertRaises(TypeError): + error_object = totest.POSYDONError(objects=test_object) + + + def test_str(self): + self.assertTrue(isroutine(self.POSYDONError.__str__)) + self.assertEqual(str(self.POSYDONError), "\ntest message on posittion") + # test passed objects + test_object1 = {'Test': 'object'} + error_object = totest.POSYDONError(objects=[test_object1]) + self.assertEqual(str(error_object), "\n") + test_object2 = totest.SingleStar() + error_object = totest.POSYDONError(objects=test_object2) + self.assertIn("OBJECT #()", str(error_object)) + test_object3 = totest.BinaryStar() + error_object = totest.POSYDONError(objects=test_object3) + self.assertIn("OBJECT #()", str(error_object)) + error_object = totest.POSYDONError(objects=[test_object1, test_object2, test_object3]) + self.assertNotIn("OBJECT #1", str(error_object)) + self.assertIn("OBJECT #2 ()", str(error_object)) + self.assertIn("OBJECT #3 ()", str(error_object)) + + +if __name__ == "__main__": + unittest.main() diff --git a/posydon/utils/posydonerror.py b/posydon/utils/posydonerror.py index f107d7864..3ac47a383 100644 --- a/posydon/utils/posydonerror.py +++ b/posydon/utils/posydonerror.py @@ -24,12 +24,18 @@ def __init__(self, message="", objects=None): ---------- message : str POSYDONError message. - objects : None or list of objects. + objects : None or list of objects A list of accompanied objects (or None if not set), that will be handled differently when `str` method is used. This error can only accept BinaryStar, SingleStar, or a list of each. """ + if not isinstance(message, str): + raise TypeError("The error message must be a string.") + if ((objects is not None) and + (not isinstance(objects, (list, SingleStar, BinaryStar)))): + raise TypeError("The error message must be None, a list, a " + "SingleStar object, or a BinaryStar object.") self.message = message # copy the objects: we must know their state at the moment of the error self.objects = copy.copy(objects) @@ -39,11 +45,11 @@ def __str__(self): """Create the text that accompanies this exception.""" result = "" if self.objects is not None: - if isinstance(self.objects,list): + if isinstance(self.objects, list): for i, obj in enumerate(self.objects): if isinstance(obj, (BinaryStar, SingleStar)): result += f"\n\nOBJECT #{i+1} ({type(obj)}):\n{str(obj)}" - elif isinstance(obj, (BinaryStar, SingleStar)): + elif isinstance(self.objects, (BinaryStar, SingleStar)): result += f"\n\nOBJECT #({type(self.objects)}):\n{str(self.objects)}" else: pass @@ -68,7 +74,20 @@ class ModelError(POSYDONError): class NumericalError(POSYDONError): """POSYDON error specific for when a binary FAILS due to limitations of numerical methods.""" -def initial_condition_message(binary,ini_params = None ): +def initial_condition_message(binary, ini_params=None): + """Generate a message with the initial conditions. + + Parameters + ---------- + binary : BinaryStar + BinaryStar object to take the initial conditions from. + ini_params : None or iterable of str + If None take the initial conditions from the binary, otherwise add + each item of it to the message. + + """ + if not isinstance(binary, BinaryStar): + raise TypeError("The binary must be a BinaryStar object.") if ini_params is None: ini_params = ["\nFailed Binary Initial Conditions:\n", f"S1 mass: {binary.star_1.mass_history[0]} \n", @@ -83,7 +102,7 @@ def initial_condition_message(binary,ini_params = None ): f"S2 natal kick array: { binary.star_2.natal_kick_array}\n"] message = "" for i in ini_params: - message += i + message += i return message From 356e4689a59960a6a200bca637886e3c98bb473b Mon Sep 17 00:00:00 2001 From: mkruckow Date: Wed, 11 Sep 2024 10:57:48 +0200 Subject: [PATCH 07/34] edit docstring --- posydon/utils/posydonerror.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/posydon/utils/posydonerror.py b/posydon/utils/posydonerror.py index 3ac47a383..4365ce79f 100644 --- a/posydon/utils/posydonerror.py +++ b/posydon/utils/posydonerror.py @@ -85,6 +85,11 @@ def initial_condition_message(binary, ini_params=None): If None take the initial conditions from the binary, otherwise add each item of it to the message. + Returns + ------- + string + The message with the initial conditions. + """ if not isinstance(binary, BinaryStar): raise TypeError("The binary must be a BinaryStar object.") From 8aaba140d214ce59434325ae2b673c452eb37b2a Mon Sep 17 00:00:00 2001 From: mkruckow Date: Wed, 11 Sep 2024 18:18:09 +0200 Subject: [PATCH 08/34] add test_posydonwarning.py --- posydon/unit_tests/utils/test_ignorereason.py | 3 +- posydon/unit_tests/utils/test_posydonerror.py | 2 +- .../unit_tests/utils/test_posydonwarning.py | 625 ++++++++++++++++++ posydon/utils/posydonwarning.py | 2 +- 4 files changed, 628 insertions(+), 4 deletions(-) create mode 100644 posydon/unit_tests/utils/test_posydonwarning.py diff --git a/posydon/unit_tests/utils/test_ignorereason.py b/posydon/unit_tests/utils/test_ignorereason.py index ae114ae05..2be81aa03 100644 --- a/posydon/unit_tests/utils/test_ignorereason.py +++ b/posydon/unit_tests/utils/test_ignorereason.py @@ -24,8 +24,7 @@ def test_dir(self): "without an update on the unit test.") def test_instance_IGNORE_REASONS_PRIORITY(self): - self.assertIsInstance(totest.IGNORE_REASONS_PRIORITY, - (list)) + self.assertIsInstance(totest.IGNORE_REASONS_PRIORITY, (list)) def test_instance_IgnoreReason(self): self.assertTrue(isclass(totest.IgnoreReason)) diff --git a/posydon/unit_tests/utils/test_posydonerror.py b/posydon/unit_tests/utils/test_posydonerror.py index ec18f2601..7bf426c95 100644 --- a/posydon/unit_tests/utils/test_posydonerror.py +++ b/posydon/unit_tests/utils/test_posydonerror.py @@ -50,7 +50,7 @@ def test_instance_NumericalError(self): self.assertTrue(isclass(totest.NumericalError)) self.assertTrue(issubclass(totest.NumericalError, totest.POSYDONError)) - def test_initial_condition_message(self): + def test_instance_initial_condition_message(self): self.assertTrue(isroutine(totest.initial_condition_message)) diff --git a/posydon/unit_tests/utils/test_posydonwarning.py b/posydon/unit_tests/utils/test_posydonwarning.py new file mode 100644 index 000000000..76c493a4d --- /dev/null +++ b/posydon/unit_tests/utils/test_posydonwarning.py @@ -0,0 +1,625 @@ +"""Unit tests of posydon/utils/posydonwarning.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the unittest module and the module which will be tested +import unittest +import posydon.utils.posydonwarning as totest + +# import other needed code for the tests +from inspect import isclass, isroutine +from io import StringIO +from contextlib import redirect_stdout, redirect_stderr + +# define test classes +class TestElements(unittest.TestCase): + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['AllPOSYDONWarnings', 'ApproximationWarning', + 'BinaryParsingWarning', 'Catch_POSYDON_Warnings', + 'ClassificationWarning', 'EvolutionWarning', + 'InappropriateValueWarning', 'IncompletenessWarning', + 'InterpolationWarning', 'MissingFilesWarning', + 'NoPOSYDONWarnings', 'OverwriteWarning', 'POSYDONWarning', + 'Pwarn', 'ReplaceValueWarning', 'SetPOSYDONWarnings', + 'UnsupportedModelWarning', '_CAUGHT_POSYDON_WARNINGS', + '_Caught_POSYDON_Warnings', '_POSYDONWarning_subclasses', + '_POSYDON_WARNINGS_REGISTRY', '__authors__', + '__builtins__', '__cached__', '__doc__', '__file__', + '__loader__', '__name__', '__package__', '__spec__', + '_apply_POSYDON_filter', '_get_POSYDONWarning_class', + '_issue_warn', 'copy', 'get_stats', 'print_stats', 'sys', + 'warnings'] + self.assertListEqual(dir(totest), elements, + msg="There might be added or removed objects " + "without an update on the unit test.") + + def test_instance_POSYDONWarning(self): + self.assertTrue(isclass(totest.POSYDONWarning)) + self.assertTrue(issubclass(totest.POSYDONWarning, Warning)) + + def test_instance_ApproximationWarning(self): + self.assertTrue(isclass(totest.ApproximationWarning)) + self.assertTrue(issubclass(totest.ApproximationWarning, + totest.POSYDONWarning)) + + def test_instance_BinaryParsingWarning(self): + self.assertTrue(isclass(totest.BinaryParsingWarning)) + self.assertTrue(issubclass(totest.BinaryParsingWarning, + totest.POSYDONWarning)) + + def test_instance_ClassificationWarning(self): + self.assertTrue(isclass(totest.ClassificationWarning)) + self.assertTrue(issubclass(totest.ClassificationWarning, + totest.POSYDONWarning)) + + def test_instance_EvolutionWarning(self): + self.assertTrue(isclass(totest.EvolutionWarning)) + self.assertTrue(issubclass(totest.EvolutionWarning, + totest.POSYDONWarning)) + + def test_instance_InappropriateValueWarning(self): + self.assertTrue(isclass(totest.InappropriateValueWarning)) + self.assertTrue(issubclass(totest.InappropriateValueWarning, + totest.POSYDONWarning)) + + def test_instance_IncompletenessWarning(self): + self.assertTrue(isclass(totest.IncompletenessWarning)) + self.assertTrue(issubclass(totest.IncompletenessWarning, + totest.POSYDONWarning)) + + def test_instance_InterpolationWarning(self): + self.assertTrue(isclass(totest.InterpolationWarning)) + self.assertTrue(issubclass(totest.InterpolationWarning, + totest.POSYDONWarning)) + + def test_instance_MissingFilesWarning(self): + self.assertTrue(isclass(totest.MissingFilesWarning)) + self.assertTrue(issubclass(totest.MissingFilesWarning, + totest.POSYDONWarning)) + + def test_instance_OverwriteWarning(self): + self.assertTrue(isclass(totest.OverwriteWarning)) + self.assertTrue(issubclass(totest.OverwriteWarning, + totest.POSYDONWarning)) + + def test_instance_ReplaceValueWarning(self): + self.assertTrue(isclass(totest.ReplaceValueWarning)) + self.assertTrue(issubclass(totest.ReplaceValueWarning, + totest.POSYDONWarning)) + + def test_instance_UnsupportedModelWarning(self): + self.assertTrue(isclass(totest.UnsupportedModelWarning)) + self.assertTrue(issubclass(totest.UnsupportedModelWarning, + totest.POSYDONWarning)) + + def test_instance_POSYDONWarning_subclasses(self): + self.assertIsInstance(totest._POSYDONWarning_subclasses, (dict)) + + def test_instance_get_POSYDONWarning_class(self): + self.assertTrue(isroutine(totest._get_POSYDONWarning_class)) + + def test_instance_POSYDON_WARNINGS_REGISTRY(self): + self.assertIsInstance(totest._POSYDON_WARNINGS_REGISTRY, (dict)) + + def test_instance_get_stats(self): + self.assertTrue(isroutine(totest.get_stats)) + + def test_instance_print_stats(self): + self.assertTrue(isroutine(totest.print_stats)) + + def test_instance_apply_POSYDON_filter(self): + self.assertTrue(isroutine(totest._apply_POSYDON_filter)) + + def test_instance_issue_warn(self): + self.assertTrue(isroutine(totest._issue_warn)) + + def test_instance_Caught_POSYDON_Warnings(self): + self.assertTrue(isclass(totest._Caught_POSYDON_Warnings)) + + def test_instance_CAUGHT_POSYDON_WARNINGS(self): + self.assertIsInstance(totest._CAUGHT_POSYDON_WARNINGS, + totest._Caught_POSYDON_Warnings) + + def test_instance_Catch_POSYDON_Warnings(self): + self.assertTrue(isclass(totest.Catch_POSYDON_Warnings)) + + def test_instance_Pwarn(self): + self.assertTrue(isroutine(totest.Pwarn)) + + def test_instance_SetPOSYDONWarnings(self): + self.assertTrue(isroutine(totest.SetPOSYDONWarnings)) + + def test_instance_NoPOSYDONWarnings(self): + self.assertTrue(isroutine(totest.NoPOSYDONWarnings)) + + def test_instance_AllPOSYDONWarnings(self): + self.assertTrue(isroutine(totest.AllPOSYDONWarnings)) + + +class TestValues(unittest.TestCase): + # check that the values fit + def test_value_POSYDONWarning_subclasses(self): + self.assertIn('ApproximationWarning', + totest._POSYDONWarning_subclasses) + + def test_value_POSYDON_WARNINGS_REGISTRY(self): + self.assertDictEqual({}, totest._POSYDON_WARNINGS_REGISTRY) + + def test_value_CAUGHT_POSYDON_WARNINGS(self): + self.assertFalse(totest._CAUGHT_POSYDON_WARNINGS.catch_warnings) + self.assertTrue(totest._CAUGHT_POSYDON_WARNINGS.record) + self.assertTrue(totest._CAUGHT_POSYDON_WARNINGS.filter_first) + self.assertListEqual(totest._CAUGHT_POSYDON_WARNINGS.caught_warnings, + []) + self.assertEqual(totest._CAUGHT_POSYDON_WARNINGS.registry, + totest._POSYDON_WARNINGS_REGISTRY) + + +class TestFunctions(unittest.TestCase): + def tearDown(self): + # empyt the global POSYDON warnings registry after each test + keys = [] + for k in totest._POSYDON_WARNINGS_REGISTRY: + keys.append(k) + for k in keys: + del totest._POSYDON_WARNINGS_REGISTRY[k] + # set POSYDON warnings back to default + totest.SetPOSYDONWarnings() + + # test functions + def test_get_POSYDONWarning_class(self): + # missing argument + with self.assertRaises(TypeError): + totest._get_POSYDONWarning_class() + # default to POSYDONWarning + self.assertEqual(totest._get_POSYDONWarning_class(""), + totest.POSYDONWarning) + self.assertEqual(totest._get_POSYDONWarning_class( + totest.POSYDONWarning), totest.POSYDONWarning) + # check subclasses of POSYDONWarning + for k,v in totest._POSYDONWarning_subclasses.items(): + self.assertEqual(totest._get_POSYDONWarning_class(k), v) + self.assertEqual(totest._get_POSYDONWarning_class(v), v) + # bad input + self.assertIsNone(totest._get_POSYDONWarning_class(1)) + + def test_get_stats(self): + self.assertEqual(totest.get_stats(), totest._POSYDON_WARNINGS_REGISTRY) + + def test_print_stats(self): + # no warnings to print + with redirect_stdout(StringIO()) as print_out: + totest.print_stats() + self.assertEqual("No POSYDON warnings occured.\n", + print_out.getvalue()) + # add an artifical entry in the warnings registry and get the printout + totest._POSYDON_WARNINGS_REGISTRY = {'Unit': 'Test'} + with redirect_stdout(StringIO()) as print_out: + totest.print_stats() + self.assertEqual("There have been POSYDON warnings in the global "+ + "registry:\n "+str(totest._POSYDON_WARNINGS_REGISTRY)+ + "\n", print_out.getvalue()) + + def test_apply_POSYDON_filter(self): + # wrong arguments + with self.assertRaises(TypeError): + totest._apply_POSYDON_filter(warning="Test") + with self.assertRaises(TypeError): + totest._apply_POSYDON_filter(warning={'message': 1}) + with self.assertRaises(TypeError): + totest._apply_POSYDON_filter(warning={'stacklevel': "Test"}) + with redirect_stdout(StringIO()) as print_out: + totest._apply_POSYDON_filter(registry="Test") + self.assertEqual("Reset registry, old was: Test\n", + print_out.getvalue()) + # check default warning + self.assertDictEqual(dict(message="No warning"), + totest._apply_POSYDON_filter()) + # check that further default warnings are filtered out but added to the + # registry + for i in range(10): + for k,v in totest._POSYDON_WARNINGS_REGISTRY.items(): + self.assertEqual(i+1, v) + self.assertIsNone(totest._apply_POSYDON_filter()) + + def test_issue_warn(self): + # wrong arguments + with self.assertRaises(TypeError): + totest._issue_warn(warning="Test") + with self.assertRaises(TypeError): + totest._issue_warn(warning={'message': 1}) + with self.assertRaises(TypeError): + totest._issue_warn(warning={'stacklevel': "Test"}) + with redirect_stdout(StringIO()) as print_out: + # it will issue a default warning on the reset registry + with redirect_stderr(StringIO()) as print_err: + totest._issue_warn(registry="Test") + self.assertEqual("Reset registry, old was: Test\n", + print_out.getvalue()) + # check default warning + self.assertIn("UserWarning: No warning", print_err.getvalue()) + # check filtered warning + self.assertIsNone(totest._issue_warn()) + + def test_Pwarn(self): + # wrong arguments + with self.assertRaises(TypeError): + totest.Pwarn() + with self.assertRaises(TypeError): + totest.Pwarn(1) + with self.assertRaises(TypeError): + totest.Pwarn("Unit", stacklevel="Test") + # check output of POSYDONwarning and UserWarning + with redirect_stderr(StringIO()) as print_err: + totest.Pwarn("Unit test", "POSYDONWarning") + self.assertIn("POSYDONWarning: 'Unit test'", print_err.getvalue()) + with redirect_stderr(StringIO()) as print_err: + totest.Pwarn("Unit test") + self.assertIn("UserWarning: Unit test", print_err.getvalue()) + + def test_SetPOSYDONWarnings(self): + totest.SetPOSYDONWarnings() + self.assertEqual("('default', None, , " + "None, 0)", str(totest.warnings.filters[0])) + + def test_NoPOSYDONWarnings(self): + totest.NoPOSYDONWarnings() + self.assertEqual("('ignore', None, , " + "None, 0)", str(totest.warnings.filters[0])) + + def test_AllPOSYDONWarnings(self): + totest.AllPOSYDONWarnings() + self.assertEqual("('always', None, , " + "None, 0)", str(totest.warnings.filters[0])) + + +class TestPOSYDONWarning(unittest.TestCase): + # test the POSYDONWarning class + def setUp(self): + # initialize an instance of the class for each test + self.POSYDONWarning = totest.POSYDONWarning() + + def test_init(self): + self.assertTrue(isroutine(self.POSYDONWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.POSYDONWarning, totest.POSYDONWarning) + self.assertEqual('', self.POSYDONWarning.message) + + def test_str(self): + self.assertTrue(isroutine(self.POSYDONWarning.__str__)) + self.assertEqual("''", str(self.POSYDONWarning)) + + +class TestApproximationWarning(unittest.TestCase): + # test the ApproximationWarning class + def setUp(self): + # initialize an instance of the class for each test + self.ApproximationWarning = totest.ApproximationWarning() + + def test_init(self): + self.assertTrue(isroutine(self.ApproximationWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.ApproximationWarning, + totest.ApproximationWarning) + self.assertEqual('', self.ApproximationWarning.message) + + +class TestBinaryParsingWarning(unittest.TestCase): + # test the BinaryParsingWarning class + def setUp(self): + # initialize an instance of the class for each test + self.BinaryParsingWarning = totest.BinaryParsingWarning() + + def test_init(self): + self.assertTrue(isroutine(self.BinaryParsingWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.BinaryParsingWarning, + totest.BinaryParsingWarning) + self.assertEqual('', self.BinaryParsingWarning.message) + + +class TestClassificationWarning(unittest.TestCase): + # test the ClassificationWarning class + def setUp(self): + # initialize an instance of the class for each test + self.ClassificationWarning = totest.ClassificationWarning() + + def test_init(self): + self.assertTrue(isroutine(self.ClassificationWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.ClassificationWarning, + totest.ClassificationWarning) + self.assertEqual('', self.ClassificationWarning.message) + + +class TestEvolutionWarning(unittest.TestCase): + # test the EvolutionWarning class + def setUp(self): + # initialize an instance of the class for each test + self.EvolutionWarning = totest.EvolutionWarning() + + def test_init(self): + self.assertTrue(isroutine(self.EvolutionWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.EvolutionWarning, totest.EvolutionWarning) + self.assertEqual('', self.EvolutionWarning.message) + + +class TestInappropriateValueWarning(unittest.TestCase): + # test the InappropriateValueWarning class + def setUp(self): + # initialize an instance of the class for each test + self.InappropriateValueWarning = totest.InappropriateValueWarning() + + def test_init(self): + self.assertTrue(isroutine(self.InappropriateValueWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.InappropriateValueWarning, + totest.InappropriateValueWarning) + self.assertEqual('', self.InappropriateValueWarning.message) + + +class TestIncompletenessWarning(unittest.TestCase): + # test the IncompletenessWarning class + def setUp(self): + # initialize an instance of the class for each test + self.IncompletenessWarning = totest.IncompletenessWarning() + + def test_init(self): + self.assertTrue(isroutine(self.IncompletenessWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.IncompletenessWarning, + totest.IncompletenessWarning) + self.assertEqual('', self.IncompletenessWarning.message) + + +class TestInterpolationWarning(unittest.TestCase): + # test the InterpolationWarning class + def setUp(self): + # initialize an instance of the class for each test + self.InterpolationWarning = totest.InterpolationWarning() + + def test_init(self): + self.assertTrue(isroutine(self.InterpolationWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.InterpolationWarning, + totest.InterpolationWarning) + self.assertEqual('', self.InterpolationWarning.message) + + +class TestMissingFilesWarning(unittest.TestCase): + # test the MissingFilesWarning class + def setUp(self): + # initialize an instance of the class for each test + self.MissingFilesWarning = totest.MissingFilesWarning() + + def test_init(self): + self.assertTrue(isroutine(self.MissingFilesWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.MissingFilesWarning, + totest.MissingFilesWarning) + self.assertEqual('', self.MissingFilesWarning.message) + + +class TestOverwriteWarning(unittest.TestCase): + # test the OverwriteWarning class + def setUp(self): + # initialize an instance of the class for each test + self.OverwriteWarning = totest.OverwriteWarning() + + def test_init(self): + self.assertTrue(isroutine(self.OverwriteWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.OverwriteWarning, + totest.OverwriteWarning) + self.assertEqual('', self.OverwriteWarning.message) + + +class TestReplaceValueWarning(unittest.TestCase): + # test the ReplaceValueWarning class + def setUp(self): + # initialize an instance of the class for each test + self.ReplaceValueWarning = totest.ReplaceValueWarning() + + def test_init(self): + self.assertTrue(isroutine(self.ReplaceValueWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.ReplaceValueWarning, + totest.ReplaceValueWarning) + self.assertEqual('', self.ReplaceValueWarning.message) + + +class TestUnsupportedModelWarning(unittest.TestCase): + # test the UnsupportedModelWarning class + def setUp(self): + # initialize an instance of the class for each test + self.UnsupportedModelWarning = totest.UnsupportedModelWarning() + + def test_init(self): + self.assertTrue(isroutine(self.UnsupportedModelWarning.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.UnsupportedModelWarning, + totest.UnsupportedModelWarning) + self.assertEqual('', self.UnsupportedModelWarning.message) + + +class Test_Caught_POSYDON_Warnings(unittest.TestCase): + # test the _Caught_POSYDON_Warnings class + def setUp(self): + # initialize an instance of the class for each test + self._Caught_POSYDON_Warnings = totest._Caught_POSYDON_Warnings() + + def tearDown(self): + # empty the cache to not print remaining records + self._Caught_POSYDON_Warnings(empty_cache=True) + # empyt the global POSYDON warnings registry after each test + keys = [] + for k in totest._POSYDON_WARNINGS_REGISTRY: + keys.append(k) + for k in keys: + del totest._POSYDON_WARNINGS_REGISTRY[k] + + def test_init(self): + self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self._Caught_POSYDON_Warnings, + totest._Caught_POSYDON_Warnings) + self.assertFalse(self._Caught_POSYDON_Warnings.catch_warnings) + self.assertListEqual([], self._Caught_POSYDON_Warnings.caught_warnings) + self.assertTrue(self._Caught_POSYDON_Warnings.record) + self.assertTrue(self._Caught_POSYDON_Warnings.filter_first) + self.assertFalse(self._Caught_POSYDON_Warnings._got_called) + self.assertDictEqual(totest._POSYDON_WARNINGS_REGISTRY, + self._Caught_POSYDON_Warnings.registry) + # bad input + with self.assertRaises(TypeError): + totest._Caught_POSYDON_Warnings(catch_warnings="Test") + with self.assertRaises(TypeError): + totest._Caught_POSYDON_Warnings(record="Test") + with self.assertRaises(TypeError): + totest._Caught_POSYDON_Warnings(filter_first="Test") + with self.assertRaises(TypeError): + totest._Caught_POSYDON_Warnings(registry="Test") + + def test_str(self): + self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.__str__)) + self.assertEqual("POSYDON warnings are shown.", + str(self._Caught_POSYDON_Warnings)) + # check with catching + caught_object = totest._Caught_POSYDON_Warnings(catch_warnings=True) + self.assertEqual("POSYDON warnings will be caught and recorded. " + "Filters are applied before recording.", + str(caught_object)) + # check without recording and own registry + test_registry = {'Unit': "Test"} + caught_object = totest._Caught_POSYDON_Warnings(catch_warnings=True, + record=False, + registry=test_registry) + self.assertEqual("POSYDON warnings will be caught and discarded. " + "Currently a private registry is used, it contains:\n" + "{'Unit': 'Test'}", str(caught_object)) + + def test_call(self): + self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.__call__)) + # bad input + with self.assertRaises(ValueError): + self._Caught_POSYDON_Warnings() + self.assertTrue(self._Caught_POSYDON_Warnings._got_called) + with self.assertRaises(TypeError): + self._Caught_POSYDON_Warnings(change_settings="Test") + with self.assertRaises(TypeError): + self._Caught_POSYDON_Warnings(change_settings={'catch_warnings': + "Test"}) + with self.assertRaises(AttributeError): + self._Caught_POSYDON_Warnings(change_settings={'Unit': "Test"}) + # change setting to catch warnings and add a new one to the record list + self._Caught_POSYDON_Warnings(change_settings={'catch_warnings': True}, + new_warning={'message': "Test"}) + self.assertTrue(self._Caught_POSYDON_Warnings.catch_warnings) + self.assertListEqual([{'message': "Test"}], + self._Caught_POSYDON_Warnings.caught_warnings) + + def test_del(self): + self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.__del__)) + # create an object with a catched warning, call the destructor and + # empty it while recording the stdout and stderr + with redirect_stderr(StringIO()) as print_err: + caught_object = totest._Caught_POSYDON_Warnings( + catch_warnings=True) + caught_object(new_warning={'message': "Unit Test"}) + caught_object.__del__() + caught_object(empty_cache=True) + self.assertIn("There are still recorded warnings:", + print_err.getvalue()) + self.assertIn("UserWarning: Unit Test", print_err.getvalue()) + + def test_got_called(self): + self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.got_called)) + self.assertFalse(self._Caught_POSYDON_Warnings.got_called()) + self._Caught_POSYDON_Warnings(empty_cache=True) + self.assertTrue(self._Caught_POSYDON_Warnings.got_called()) + + def test_has_records(self): + self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.has_records)) + self.assertFalse(self._Caught_POSYDON_Warnings.has_records()) + self._Caught_POSYDON_Warnings(change_settings={'catch_warnings': True}, + new_warning={'message': "Unit Test"}) + self.assertTrue(self._Caught_POSYDON_Warnings.has_records()) + + def test_get_cache(self): + self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.get_cache)) + self.assertListEqual([], self._Caught_POSYDON_Warnings.get_cache()) + + def test_reset_cache(self): + self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.reset_cache)) + self._Caught_POSYDON_Warnings(change_settings={'catch_warnings': True}, + new_warning={'message': "Unit Test"}) + self.assertListEqual([{'message': "Unit Test"}], + self._Caught_POSYDON_Warnings.caught_warnings) + self._Caught_POSYDON_Warnings.reset_cache() + self.assertListEqual([], self._Caught_POSYDON_Warnings.caught_warnings) + + +class TestCatch_POSYDON_Warnings(unittest.TestCase): + # test the Catch_POSYDON_Warnings class + def setUp(self): + # initialize an instance of the class for each test + self.Catch_POSYDON_Warnings = totest.Catch_POSYDON_Warnings() + + def tearDown(self): + # empyt the global POSYDON warnings registry after each test + keys = [] + for k in totest._POSYDON_WARNINGS_REGISTRY: + keys.append(k) + for k in keys: + del totest._POSYDON_WARNINGS_REGISTRY[k] + + def test_init(self): + self.assertTrue(isroutine(self.Catch_POSYDON_Warnings.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertTrue(self.Catch_POSYDON_Warnings.catch_warnings) + self.assertTrue(self.Catch_POSYDON_Warnings.record) + self.assertTrue(self.Catch_POSYDON_Warnings.filter_first) + self.assertIsNone(self.Catch_POSYDON_Warnings.context_registry) + self.assertIsNone(self.Catch_POSYDON_Warnings.python_catch) + # check other inputs + catch_object = totest.Catch_POSYDON_Warnings(own_registry=True, + use_python_catch=True) + self.assertDictEqual({}, catch_object.context_registry) + self.assertIsInstance(catch_object.python_catch, + totest.warnings.catch_warnings) + + def test_enter_exit(self): + self.assertTrue(isroutine(self.Catch_POSYDON_Warnings.__enter__)) + + def test_exit(self): + self.assertTrue(isroutine(self.Catch_POSYDON_Warnings.__exit__)) + + def test_context(self): + with totest.Catch_POSYDON_Warnings() as cpw: + self.assertEqual(totest._CAUGHT_POSYDON_WARNINGS, cpw) + + +if __name__ == "__main__": + unittest.main() diff --git a/posydon/utils/posydonwarning.py b/posydon/utils/posydonwarning.py index 66bb4d70a..a645efba0 100644 --- a/posydon/utils/posydonwarning.py +++ b/posydon/utils/posydonwarning.py @@ -401,7 +401,7 @@ def __del__(self): if len(self.caught_warnings)>0: # If there are recorded warnings issue them. self.catch_warnings = False - print("There are still recorded warnings:") + print("There are still recorded warnings:", file=sys.stderr) for w in self.caught_warnings: w["stacklevel"] = 2 if self.filter_first: From d065f677479b5a8ce05509a977190b7163562b77 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Thu, 12 Sep 2024 12:04:06 +0200 Subject: [PATCH 09/34] update readme --- posydon/unit_tests/README.md | 59 +++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/posydon/unit_tests/README.md b/posydon/unit_tests/README.md index a469ca052..425b32e96 100644 --- a/posydon/unit_tests/README.md +++ b/posydon/unit_tests/README.md @@ -8,4 +8,61 @@ The tests in each `TESTFILE` can be run via If you like to run all unit tests you can use - python -m unittest $PATH_TO_POSYDON/posydon/unit_tests/*/*.py \ No newline at end of file + python -m unittest $PATH_TO_POSYDON/posydon/unit_tests/*/*.py + +## File structure + +As already visible from the commands how to run all unit tests we have in the `unit_tests` directory individual directories to combine several tests into one suite. It is a natural choice to reuse the topics used in the code itself. For example all the unit tests for files inside `$PATH_TO_POSYDON/posydon/utils` are put in `$PATH_TO_POSYDON/posydon/unit_tests/utils`. In those suits, there should be a file for each code file, which is simply extended by the prefix `test_`, e.g. `test_posydonerror.py` contains the unit tests of `posydonerror.py`. If the code file is in a subdirectory, may add this to the prefix. *I'd discourage to create deep directory structures inside the `unit_tests`.* + +## How to add a new unit test + +There is a template file `$PATH_TO_POSYDON/posydon/unit_tests/test_template.py`, which can be copied. Please rename/replace the copy to fulfill the [File structure](#file-structure). Please update the doc-string, the author list, the imported modules, and the test classes. + +### Conventions + +To keep things in a common style, here are some conventions to follow. You can never make too many tests as long as they are unique! + +#### Doc string + +Each test file should have a doc-string. This should at least contain the information which part of the code will be tested. + +#### Author list + +Like in code files we add an author list to this files to let others know whom to ask when doing changes in this file. + +#### Imports + +We always need to import the `unittest` module. Beside that we always need to import the module we'd like to test. *I recommend to import it with the local name `totest`.* + +There might be additional modules/functions needed to do the testing. *I recommend to not import stuff, which is already imported in the module, you like to test, instead use the reference in the tested module, e.g. if the module to test imports X, access it via `totest.X` in the unit test, to ensure using the same code, which is not subject to this test.* + +#### Test classes + +According to [python `unittest` module](https://docs.python.org/3/library/unittest.html), there are conventions to follow, when creating test classes and their functions. It should be noted, that there is no guarantee for the order the tests are run in. Thus, each test should be considered a stand alone. + +#### Check the elements of a module + +The first thing to check is the completeness and types of the module elements. *I suggest to use the build-in function `dir`.* All variables should be tested for their type via `assertIsInstance`. All classes and functions can be tested for their type by making us of `isclass` and `isroutine` form the `inspect` module, which should return `True`. *I suggest to put all those checks into one test class with functions for each element.* + +#### Check values + +Beside the existence and the type of a variable, should verify the integrity of it's default value. *I suggest to put all those value checks into one test class with a function for each variable.* Depending on the kind of variable different tests can be done, e.g. a fixed value for a constant, a range of a changeable variable, or needed items of a list ... + +#### Check functions + +Function in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions change variables, please create a `setUp` and/or `tearDown` function. + +#### Check classes + +Each class inside a module should be get its components checked like a module itself. *I suggest to have a test class for each class in the tested module and the test of each class function should get an own test function.* Again, `setUp` and/or `tearDown` functions should be used to ensure that all tests run under the same conditions. + +### Calling the unit test + +Each test file should include the following lines at the end to allow the to run it as standalone: + + if __name__ == "__main__": + unittest.main() + +## How to update a unit test + +First, check what is already there. Second, edit or add new stuff following the [conventions](#conventions). \ No newline at end of file From 6ae622c47816abc4b336ae511bb1c62b596b1f97 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Fri, 13 Sep 2024 17:12:59 +0200 Subject: [PATCH 10/34] add test_data_download.py --- posydon/unit_tests/README.md | 9 + .../unit_tests/utils/test_data_download.py | 159 ++++++++++++++++++ posydon/utils/data_download.py | 2 +- 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 posydon/unit_tests/utils/test_data_download.py diff --git a/posydon/unit_tests/README.md b/posydon/unit_tests/README.md index 425b32e96..cf1a2d2c7 100644 --- a/posydon/unit_tests/README.md +++ b/posydon/unit_tests/README.md @@ -52,6 +52,15 @@ Beside the existence and the type of a variable, should verify the integrity of Function in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions change variables, please create a `setUp` and/or `tearDown` function. +Functions may include prints statements. In such cases it is useful to redirect the data stream going there into a variable to be able to validate the output (there is a similar context manager to redirect `stderr`). Here an example code: + + from io import StringIO + from contextlib import redirect_stdout + ... + with redirect_stdout(StringIO()) as print_out: + totest.function_with_print_statements() + self.assertEqual("Text of first print statement.\nText of second print statement.\n", print_out.getvalue()) + #### Check classes Each class inside a module should be get its components checked like a module itself. *I suggest to have a test class for each class in the tested module and the test of each class function should get an own test function.* Again, `setUp` and/or `tearDown` functions should be used to ensure that all tests run under the same conditions. diff --git a/posydon/unit_tests/utils/test_data_download.py b/posydon/unit_tests/utils/test_data_download.py new file mode 100644 index 000000000..af48bb14d --- /dev/null +++ b/posydon/unit_tests/utils/test_data_download.py @@ -0,0 +1,159 @@ +"""Unit tests of posydon/utils/data_download.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the unittest module and the module which will be tested +import unittest +import posydon.utils.data_download as totest + +# import other needed code for the tests +from contextlib import redirect_stdout +from inspect import isclass, isroutine +from io import StringIO +from shutil import rmtree +import sys + +DO_DOWNLOAD = False + +# define test classes +class TestElements(unittest.TestCase): + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['PATH_TO_POSYDON_DATA', 'ProgressBar', '__authors__', + '__builtins__', '__cached__', '__doc__', '__file__', + '__loader__', '__name__', '__package__', '__spec__', + 'data_download', 'data_url', 'file', 'hashlib', + 'original_md5', 'os', 'progressbar', 'tarfile', 'tqdm', + 'urllib'] + self.assertListEqual(dir(totest), elements, + msg="There might be added or removed objects " + "without an update on the unit test.") + + def test_instance_PATH_TO_POSYDON_DATA(self): + self.assertIsInstance(totest.PATH_TO_POSYDON_DATA, (str, bytes, + totest.os.PathLike)) + + def test_instance_file(self): + self.assertIsInstance(totest.file, (str, bytes, totest.os.PathLike)) + + def test_instance_data_url(self): + self.assertIsInstance(totest.data_url, (str, bytes, + totest.os.PathLike)) + + def test_instance_original_md5(self): + self.assertIsInstance(totest.original_md5, (str, bytes, + totest.os.PathLike)) + + def test_instance_ProgressBar(self): + self.assertTrue(isclass(totest.ProgressBar)) + + def test_instance_data_download(self): + self.assertTrue(isroutine(totest.data_download)) + + +class TestValues(unittest.TestCase): + # check that the values fit + def test_value_PATH_TO_POSYDON_DATA(self): + self.assertIn("POSYDON_data", totest.PATH_TO_POSYDON_DATA) + + def test_value_file(self): + self.assertIn("POSYDON_data.tar.gz", totest.file) + + def test_value_data_url(self): + self.assertEqual("https://zenodo.org/record/6655751/files/" + "POSYDON_data.tar.gz", totest.data_url) + + def test_value_original_md5(self): + self.assertIn("8873544d9a568ebb85bccffbf1bdcd99", totest.original_md5) + + +class TestFunctions(unittest.TestCase): + # test functions + def test_data_download(self): + # bad input + with self.assertRaises(TypeError): + totest.data_download(file={}) + with redirect_stdout(StringIO()) as print_out: + totest.data_download(file="./", verbose=True) + self.assertEqual("POSYDON data alraedy exists at ./\n", + print_out.getvalue()) + + @unittest.skipUnless(DO_DOWNLOAD, "Usually, skip the test on the actual " + "download.") + def test_data_download(self): + # real download: may add skip option + tmp_path = "./tmp" + test_path = totest.os.path.join(tmp_path, "POSYDON_data") + if not totest.os.path.exists(test_path): + totest.os.makedirs(test_path) + totest.PATH_TO_POSYDON_DATA = test_path + with redirect_stdout(StringIO()) as print_out: + totest.data_download(file=test_path+".tar.gz", verbose=True) + self.assertIn("Downloading POSYDON data from Zenodo to " + f"PATH_TO_POSYDON_DATA={test_path}", + print_out.getvalue()) + self.assertIn("MD5 verified.", print_out.getvalue()) + self.assertIn("Extracting POSYDON data from tar file...", + print_out.getvalue()) + self.assertIn("Removed downloaded tar file.", print_out.getvalue()) + with self.assertRaises(FileNotFoundError): + totest.original_md5 += '_' + with redirect_stdout(StringIO()) as print_out: + totest.data_download(file=test_path+".tar.gz", verbose=True) + if totest.original_md5[-1]=='_': + totest.original_md5 = totest.original_md5[:-1] + self.assertIn("Failed to read the tar.gz file for MD5 verificaton, " + "cannot guarantee file integrity (this error seems to " + "happen only on macOS).", print_out.getvalue()) + rmtree(tmp_path) + + +class TestProgressBar(unittest.TestCase): + # test the ProgressBar class + def setUp(self): + # initialize an instance of the class for each test + self.ProgressBar = totest.ProgressBar() + + def tearDown(self): + def finish_remove(pbar): + # finish progress and remove the bar + pbar.finish(end='\r' + ' ' * pbar.term_width + '\r') + + # finish, remove, and reset pbar if needed + if self.ProgressBar.pbar: + finish_remove(self.ProgressBar.pbar) + self.ProgressBar.pbar = None + + def test_init(self): + self.assertTrue(isroutine(self.ProgressBar.__init__)) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + self.assertIsInstance(self.ProgressBar, totest.ProgressBar) + self.assertIsNone(self.ProgressBar.pbar) + self.assertIsInstance(self.ProgressBar.widgets, list) + + def test_call(self): + self.assertTrue(isroutine(self.ProgressBar.__call__)) + # missing argument + with self.assertRaises(TypeError): + self.ProgressBar() + # bad input + with self.assertRaises(TypeError): + self.ProgressBar("Test", 1, 1) + # the progressbar starts before the error, hence tearDown + self.tearDown() + with self.assertRaises(TypeError): + self.ProgressBar(1, "Test", 1) + # the progressbar starts before the error, hence tearDown + self.tearDown() + with self.assertRaises(TypeError): + self.ProgressBar(1, 1, "Test") + self.ProgressBar(1, 1, 2) + self.assertAlmostEqual(self.ProgressBar.pbar.percentage, 50.0) + + +if __name__ == "__main__": + unittest.main() diff --git a/posydon/utils/data_download.py b/posydon/utils/data_download.py index 773dc1061..4426f5666 100644 --- a/posydon/utils/data_download.py +++ b/posydon/utils/data_download.py @@ -33,7 +33,7 @@ def __init__(self): def __call__(self, block_num, block_size, total_size): if not self.pbar: self.pbar=progressbar.ProgressBar(widgets=self.widgets, - maxval=total_size) + max_value=total_size) self.pbar.start() downloaded = block_num * block_size From c7c61b5035d7b8e57f90117a3a4c8b9bc9887df1 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Wed, 25 Sep 2024 17:14:22 +0200 Subject: [PATCH 11/34] transfer to pytest (outstanding: test_posydonwarning.py, test_data_download.py) --- posydon/unit_tests/README.md | 73 +++- posydon/unit_tests/test_template.py | 20 +- posydon/unit_tests/utils/test_constants.py | 411 +++++++++--------- posydon/unit_tests/utils/test_ignorereason.py | 115 ++--- .../utils/test_limits_thresholds.py | 100 +++-- posydon/unit_tests/utils/test_posydonerror.py | 214 +++++---- posydon/utils/posydonerror.py | 4 +- 7 files changed, 513 insertions(+), 424 deletions(-) diff --git a/posydon/unit_tests/README.md b/posydon/unit_tests/README.md index cf1a2d2c7..447a60825 100644 --- a/posydon/unit_tests/README.md +++ b/posydon/unit_tests/README.md @@ -1,18 +1,22 @@ # POSYDON: unit tests -Here we collect our unit tests for the POSYDON code. We are using the [python `unittest` module](https://docs.python.org/3/library/unittest.html). +Here we collect our unit tests for the POSYDON code. We are using the [pytest package](https://docs.pytest.org/en/stable/index.html). + +Before using it you need to install pytest or consider to upgrade it + + pip install -U pytest The tests in each `TESTFILE` can be run via - python -m unittest TESTFILE + pytest TESTFILE If you like to run all unit tests you can use - python -m unittest $PATH_TO_POSYDON/posydon/unit_tests/*/*.py + pytest $PATH_TO_POSYDON/posydon/unit_tests/ ## File structure -As already visible from the commands how to run all unit tests we have in the `unit_tests` directory individual directories to combine several tests into one suite. It is a natural choice to reuse the topics used in the code itself. For example all the unit tests for files inside `$PATH_TO_POSYDON/posydon/utils` are put in `$PATH_TO_POSYDON/posydon/unit_tests/utils`. In those suits, there should be a file for each code file, which is simply extended by the prefix `test_`, e.g. `test_posydonerror.py` contains the unit tests of `posydonerror.py`. If the code file is in a subdirectory, may add this to the prefix. *I'd discourage to create deep directory structures inside the `unit_tests`.* +As already visible from the commands how to run all unit tests we have a the `unit_tests` directory. In there are individual directories to combine several tests into one suite. It is a natural choice to reuse the topics used in the code itself. For example all the unit tests for files inside `$PATH_TO_POSYDON/posydon/utils` are put in `$PATH_TO_POSYDON/posydon/unit_tests/utils`. In those suits, there should be a file for each code file, which is simply extended by the prefix `test_`, e.g. `test_posydonerror.py` contains the unit tests of `posydonerror.py`. If the code file is in a subdirectory, may add this to the prefix. *I'd discourage to create deep directory structures inside the `unit_tests`.* ## How to add a new unit test @@ -28,29 +32,33 @@ Each test file should have a doc-string. This should at least contain the inform #### Author list -Like in code files we add an author list to this files to let others know whom to ask when doing changes in this file. +Like in code files we add an author list to those files to let others know whom to ask when doing changes in such a file. #### Imports -We always need to import the `unittest` module. Beside that we always need to import the module we'd like to test. *I recommend to import it with the local name `totest`.* +We always need to import the module we'd like to test. *I recommend to import it with the local name `totest`.* There might be additional modules/functions needed to do the testing. *I recommend to not import stuff, which is already imported in the module, you like to test, instead use the reference in the tested module, e.g. if the module to test imports X, access it via `totest.X` in the unit test, to ensure using the same code, which is not subject to this test.* +#### Test functions + +For small single test you can simply define a single test function. The name should start with the prefix `test`. *I recommend to use the prefix `test_`.* The actual test is usually done by an assert statement. Usually, there will be several tests collected in a [class](#test-classes), thus single functions will be rare. + #### Test classes -According to [python `unittest` module](https://docs.python.org/3/library/unittest.html), there are conventions to follow, when creating test classes and their functions. It should be noted, that there is no guarantee for the order the tests are run in. Thus, each test should be considered a stand alone. +To group tests it is useful to create classes which contain several test functions. All test classes should have the prefix `Test`. *I recommend to continue with a capital letter after the prefix.* In each class there should be test functions, again having a prefix `test`. *I recommend to use the prefix `test_`.* It should be noted, that the test functions inside a class require a parameter containing the class object. *I recommend to use the standard `self`.* It should be noted, that there is no guarantee for the order the tests are run in. Thus, each test should be considered a stand alone. -#### Check the elements of a module +##### Check the elements of a module -The first thing to check is the completeness and types of the module elements. *I suggest to use the build-in function `dir`.* All variables should be tested for their type via `assertIsInstance`. All classes and functions can be tested for their type by making us of `isclass` and `isroutine` form the `inspect` module, which should return `True`. *I suggest to put all those checks into one test class with functions for each element.* +The first thing to check is the completeness and types of the module elements. *I suggest to use the build-in function `dir`.* All variables should be tested for their type via `isinstance`. All classes and functions can be tested for their type by making us of `isclass` and `isroutine` form the `inspect` module, which should return `True`. *I suggest to put all those checks into one test class with functions for each element.* -#### Check values +##### Check values -Beside the existence and the type of a variable, should verify the integrity of it's default value. *I suggest to put all those value checks into one test class with a function for each variable.* Depending on the kind of variable different tests can be done, e.g. a fixed value for a constant, a range of a changeable variable, or needed items of a list ... +Beside the existence and the type of a variable, we should verify the integrity of it's default value. *I suggest to put all those value checks into one test class with a function for each variable.* Depending on the kind of variable different tests can be done, e.g. a fixed value for a constant, a range of a changeable variable, or needed items of a list ... -#### Check functions +##### Check functions -Function in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions change variables, please create a `setUp` and/or `tearDown` function. +Function in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions need variables may [use fixtures](#using-fixtures). Functions may include prints statements. In such cases it is useful to redirect the data stream going there into a variable to be able to validate the output (there is a similar context manager to redirect `stderr`). Here an example code: @@ -61,17 +69,44 @@ Functions may include prints statements. In such cases it is useful to redirect totest.function_with_print_statements() self.assertEqual("Text of first print statement.\nText of second print statement.\n", print_out.getvalue()) -#### Check classes +##### Check classes Each class inside a module should be get its components checked like a module itself. *I suggest to have a test class for each class in the tested module and the test of each class function should get an own test function.* Again, `setUp` and/or `tearDown` functions should be used to ensure that all tests run under the same conditions. -### Calling the unit test +#### Using fixtures + +You can define [fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html) at any level. You need to decorate them with the [`pytest.fixture` function](https://docs.pytest.org/en/stable/reference/reference.html#pytest-fixture), which needs to be imported, via -Each test file should include the following lines at the end to allow the to run it as standalone: + @pytest.fixture - if __name__ == "__main__": - unittest.main() +#### Catching raised errors + +Pytest has the [context manager `pytest.raises`](https://docs.pytest.org/en/stable/reference/reference.html#pytest-raises) to catch raised errors. You use it like other context managers via a `with` statement. Beside the expected exception, you can specify a `match`, which will be checked against the error message. The context object can be used to check for more details of the raised error. + +### Check that it can fail + +Whenever you wrote a test, it should succeed on existing code. But at the other hand you should make a test that it can fail, otherwise the test won't do its work. ## How to update a unit test -First, check what is already there. Second, edit or add new stuff following the [conventions](#conventions). \ No newline at end of file +First, check what is already there. Second, edit or add new stuff following the [conventions](#conventions) and test that it works by let it fail once. + +## Check code coverage + +There is a plug-in for `pytest` to measure the coverage of executed code, called [pytest-cov](https://pypi.org/project/pytest-cov). It can be installed via + + pip install pytest-cov + +and run by including the `cov` option in the `pytest` call + + pytest TESTFILE --cov=MODULE + +Strictly speaking it runs the `pytest` inside of [coverage](https://coverage.readthedocs.io). Thus you can run it although via + + coverage run -m pytest TESTFILE + +*I suggest to use the `--branch` option, which is `--cov-branch` in pytest.* The latter version to run the test has the advantage that you can make use of all the option provided by `coverage`, while running it through the plug-in will give you only access to the options implemented in there. On the other hand, running it with the plug-in will exclude the test code from coverage and give you the coverage report together with the report of the tests, while running it via `coverage` the report in the file `.coverage` needs to readout via + + coverage report + +*I suggest to use the `-m` option to show uncovered lines, which is `--cov-report term-missing` in pytest.* \ No newline at end of file diff --git a/posydon/unit_tests/test_template.py b/posydon/unit_tests/test_template.py index b3df0a59f..093f1fd30 100644 --- a/posydon/unit_tests/test_template.py +++ b/posydon/unit_tests/test_template.py @@ -5,18 +5,18 @@ "John Doe " ] -# import the unittest module and the module which will be tested -import unittest +# import the module which will be tested import posydon.user_modules.my_flow_chart_example as totest -# import other needed code for the tests -# not needed +# import other needed code for the tests, which is not already imported in the +# module you like to test -# define test classes -class TestCode(unittest.TestCase): - def test_name(self): - self.assertTrue(True) +# define single test functions +def test_name(): + pass -if __name__ == "__main__": - unittest.main() +# define test classes collecting several test functions +class TestClass: + def test_name(self): + assert True diff --git a/posydon/unit_tests/utils/test_constants.py b/posydon/unit_tests/utils/test_constants.py index 7c0b76af6..07488f619 100644 --- a/posydon/unit_tests/utils/test_constants.py +++ b/posydon/unit_tests/utils/test_constants.py @@ -5,15 +5,15 @@ "Matthias Kruckow " ] -# import the unittest module and the module which will be tested -import unittest +# import the module which will be tested import posydon.utils.constants as totest -# import other needed code for the tests -# not needed +# import other needed code for the tests, which is not already imported in the +# module you like to test +from pytest import approx -# define test classes -class TestElements(unittest.TestCase): +# define test classes collecting several test functions +class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): elements = ['H_weight', 'He_weight', 'Lsun', 'Lsun33', 'Msun', @@ -30,462 +30,461 @@ def test_dir(self): 'planck_h', 'qe', 'r_earth', 'r_jupiter', 'rad2a', 'rbohr', 'rhonuc', 'rsol', 'secyer', 'semimajor_axis_jupiter', 'ssol', 'standard_cgrav', 'weinfre', 'weinlam'] - self.assertListEqual(dir(totest), elements, - msg="There might be added or removed objects " - "without an update on the unit test.") + assert dir(totest) == elements, "There might be added or removed "\ + "objects without an update on the unit test." def test_instance_pi(self): - self.assertIsInstance(totest.pi, (float, int)) + assert isinstance(totest.pi, (float,int)),\ + "pi is of type: "+str(type(totest.pi)) def test_instance_a2rad(self): - self.assertIsInstance(totest.a2rad, (float, int)) + assert isinstance(totest.a2rad, (float,int)),\ + "a2rad is of type: "+str(type(totest.a2rad)) def test_instance_rad2a(self): - self.assertIsInstance(totest.rad2a, (float, int)) + assert isinstance(totest.rad2a, (float,int)),\ + "rad2a is of type: "+str(type(totest.rad2a)) def test_instance_standard_cgrav(self): - self.assertIsInstance(totest.standard_cgrav, (float, int)) + assert isinstance(totest.standard_cgrav, (float,int)),\ + "standard_cgrav is of type: "+\ + str(type(totest.standard_cgrav)) def test_instance_planck_h(self): - self.assertIsInstance(totest.planck_h, (float, int)) + assert isinstance(totest.planck_h, (float,int)),\ + "planck_h is of type: "+str(type(totest.planck_h)) def test_instance_hbar(self): - self.assertIsInstance(totest.hbar, (float, int)) + assert isinstance(totest.hbar, (float,int)),\ + "hbar is of type: "+str(type(totest.hbar)) def test_instance_qe(self): - self.assertIsInstance(totest.qe, (float, int)) + assert isinstance(totest.qe, (float,int)),\ + "qe is of type: "+str(type(totest.qe)) def test_instance_avo(self): - self.assertIsInstance(totest.avo, (float, int)) + assert isinstance(totest.avo, (float,int)),\ + "avo is of type: "+str(type(totest.avo)) def test_instance_clight(self): - self.assertIsInstance(totest.clight, (float, int)) + assert isinstance(totest.clight, (float,int)),\ + "clight is of type: "+str(type(totest.clight)) def test_instance_kerg(self): - self.assertIsInstance(totest.kerg, (float, int)) + assert isinstance(totest.kerg, (float,int)),\ + "kerg is of type: "+str(type(totest.kerg)) def test_instance_boltzm(self): - self.assertIsInstance(totest.boltzm, (float, int)) + assert isinstance(totest.boltzm, (float,int)),\ + "boltzm is of type: "+str(type(totest.boltzm)) def test_instance_cgas(self): - self.assertIsInstance(totest.cgas, (float, int)) + assert isinstance(totest.cgas, (float,int)),\ + "cgas is of type: "+str(type(totest.cgas)) def test_instance_kev(self): - self.assertIsInstance(totest.kev, (float, int)) + assert isinstance(totest.kev, (float,int)),\ + "kev is of type: "+str(type(totest.kev)) def test_instance_amu(self): - self.assertIsInstance(totest.amu, (float, int)) + assert isinstance(totest.amu, (float,int)),\ + "amu is of type: "+str(type(totest.amu)) def test_instance_mn(self): - self.assertIsInstance(totest.mn, (float, int)) + assert isinstance(totest.mn, (float,int)),\ + "mn is of type: "+str(type(totest.mn)) def test_instance_mp(self): - self.assertIsInstance(totest.mp, (float, int)) + assert isinstance(totest.mp, (float,int)),\ + "mp is of type: "+str(type(totest.mp)) def test_instance_me(self): - self.assertIsInstance(totest.me, (float, int)) + assert isinstance(totest.me, (float,int)),\ + "me is of type: "+str(type(totest.me)) def test_instance_rbohr(self): - self.assertIsInstance(totest.rbohr, (float, int)) + assert isinstance(totest.rbohr, (float,int)),\ + "rbohr is of type: "+str(type(totest.rbohr)) def test_instance_fine(self): - self.assertIsInstance(totest.fine, (float, int)) + assert isinstance(totest.fine, (float,int)),\ + "fine is of type: "+str(type(totest.fine)) def test_instance_hion(self): - self.assertIsInstance(totest.hion, (float, int)) + assert isinstance(totest.hion, (float,int)),\ + "hion is of type: "+str(type(totest.hion)) def test_instance_ev2erg(self): - self.assertIsInstance(totest.ev2erg, (float, int)) + assert isinstance(totest.ev2erg, (float,int)),\ + "ev2erg is of type: "+str(type(totest.ev2erg)) def test_instance_inversecm2erg(self): - self.assertIsInstance(totest.inversecm2erg, (float, int)) + assert isinstance(totest.inversecm2erg, (float,int)),\ + "inversecm2erg is of type: "+str(type(totest.inversecm2erg)) def test_instance_mev_to_ergs(self): - self.assertIsInstance(totest.mev_to_ergs, (float, int)) + assert isinstance(totest.mev_to_ergs, (float,int)),\ + "mev_to_ergs is of type: "+str(type(totest.mev_to_ergs)) def test_instance_mev_amu(self): - self.assertIsInstance(totest.mev_amu, (float, int)) + assert isinstance(totest.mev_amu, (float,int)),\ + "mev_amu is of type: "+str(type(totest.mev_amu)) def test_instance_Qconv(self): - self.assertIsInstance(totest.Qconv, (float, int)) + assert isinstance(totest.Qconv, (float,int)),\ + "Qconv is of type: "+str(type(totest.Qconv)) def test_instance_boltz_sigma(self): - self.assertIsInstance(totest.boltz_sigma, (float, int)) + assert isinstance(totest.boltz_sigma, (float,int)),\ + "boltz_sigma is of type: "+str(type(totest.boltz_sigma)) def test_instance_crad(self): - self.assertIsInstance(totest.crad, (float, int)) + assert isinstance(totest.crad, (float,int)),\ + "crad is of type: "+str(type(totest.crad)) def test_instance_ssol(self): - self.assertIsInstance(totest.ssol, (float, int)) + assert isinstance(totest.ssol, (float,int)),\ + "ssol is of type: "+str(type(totest.ssol)) def test_instance_asol(self): - self.assertIsInstance(totest.asol, (float, int)) + assert isinstance(totest.asol, (float,int)),\ + "asol is of type: "+str(type(totest.asol)) def test_instance_weinlam(self): - self.assertIsInstance(totest.weinlam, (float, int)) + assert isinstance(totest.weinlam, (float,int)),\ + "weinlam is of type: "+str(type(totest.weinlam)) def test_instance_weinfre(self): - self.assertIsInstance(totest.weinfre, (float, int)) + assert isinstance(totest.weinfre, (float,int)),\ + "weinfre is of type: "+str(type(totest.weinfre)) def test_instance_rhonuc(self): - self.assertIsInstance(totest.rhonuc, (float, int)) + assert isinstance(totest.rhonuc, (float,int)),\ + "rhonuc is of type: "+str(type(totest.rhonuc)) def test_instance_Zsun(self): - self.assertIsInstance(totest.Zsun, (float, int)) + assert isinstance(totest.Zsun, (float,int)),\ + "Zsun is of type: "+str(type(totest.Zsun)) def test_instance_msol(self): - self.assertIsInstance(totest.msol, (float, int)) + assert isinstance(totest.msol, (float,int)),\ + "msol is of type: "+str(type(totest.msol)) def test_instance_rsol(self): - self.assertIsInstance(totest.rsol, (float, int)) + assert isinstance(totest.rsol, (float,int)),\ + "rsol is of type: "+str(type(totest.rsol)) def test_instance_lsol(self): - self.assertIsInstance(totest.lsol, (float, int)) + assert isinstance(totest.lsol, (float,int)),\ + "lsol is of type: "+str(type(totest.lsol)) def test_instance_agesol(self): - self.assertIsInstance(totest.agesol, (float, int)) + assert isinstance(totest.agesol, (float,int)),\ + "agesol is of type: "+str(type(totest.agesol)) def test_instance_Msun(self): - self.assertIsInstance(totest.Msun, (float, int)) + assert isinstance(totest.Msun, (float,int)),\ + "Msun is of type: "+str(type(totest.Msun)) def test_instance_Rsun(self): - self.assertIsInstance(totest.Rsun, (float, int)) + assert isinstance(totest.Rsun, (float,int)),\ + "Rsun is of type: "+str(type(totest.Rsun)) def test_instance_Lsun(self): - self.assertIsInstance(totest.Lsun, (float, int)) + assert isinstance(totest.Lsun, (float,int)),\ + "Lsun is of type: "+str(type(totest.Lsun)) def test_instance_Msun33(self): - self.assertIsInstance(totest.Msun33, (float, int)) + assert isinstance(totest.Msun33, (float,int)),\ + "Msun33 is of type: "+str(type(totest.Msun33)) def test_instance_Rsun11(self): - self.assertIsInstance(totest.Rsun11, (float, int)) + assert isinstance(totest.Rsun11, (float,int)),\ + "Rsun11 is of type: "+str(type(totest.Rsun11)) def test_instance_Lsun33(self): - self.assertIsInstance(totest.Lsun33, (float, int)) + assert isinstance(totest.Lsun33, (float,int)),\ + "Lsun33 is of type: "+str(type(totest.Lsun33)) def test_instance_ly(self): - self.assertIsInstance(totest.ly, (float, int)) + assert isinstance(totest.ly, (float,int)),\ + "ly is of type: "+str(type(totest.ly)) def test_instance_pc(self): - self.assertIsInstance(totest.pc, (float, int)) + assert isinstance(totest.pc, (float,int)),\ + "pc is of type: "+str(type(totest.pc)) def test_instance_secyer(self): - self.assertIsInstance(totest.secyer, (float, int)) + assert isinstance(totest.secyer, (float,int)),\ + "secyer is of type: "+str(type(totest.secyer)) def test_instance_dayyer(self): - self.assertIsInstance(totest.dayyer, (float, int)) + assert isinstance(totest.dayyer, (float,int)),\ + "dayyer is of type: "+str(type(totest.dayyer)) def test_instance_age_of_universe(self): - self.assertIsInstance(totest.age_of_universe, (float, int)) + assert isinstance(totest.age_of_universe, (float,int)),\ + "age_of_universe is of type: "+str(type(totest.age_of_universe)) def test_instance_Teffsol(self): - self.assertIsInstance(totest.Teffsol, (float, int)) + assert isinstance(totest.Teffsol, (float,int)),\ + "Teffsol is of type: "+str(type(totest.Teffsol)) def test_instance_loggsol(self): - self.assertIsInstance(totest.loggsol, (float, int)) + assert isinstance(totest.loggsol, (float,int)),\ + "loggsol is of type: "+str(type(totest.loggsol)) def test_instance_mbolsun(self): - self.assertIsInstance(totest.mbolsun, (float, int)) + assert isinstance(totest.mbolsun, (float,int)),\ + "mbolsun is of type: "+str(type(totest.mbolsun)) def test_instance_mbolsol(self): - self.assertIsInstance(totest.mbolsol, (float, int)) + assert isinstance(totest.mbolsol, (float,int)),\ + "mbolsol is of type: "+str(type(totest.mbolsol)) def test_instance_m_earth(self): - self.assertIsInstance(totest.m_earth, (float, int)) + assert isinstance(totest.m_earth, (float,int)),\ + "m_earth is of type: "+str(type(totest.m_earth)) def test_instance_r_earth(self): - self.assertIsInstance(totest.r_earth, (float, int)) + assert isinstance(totest.r_earth, (float,int)),\ + "r_earth is of type: "+str(type(totest.r_earth)) def test_instance_au(self): - self.assertIsInstance(totest.au, (float, int)) + assert isinstance(totest.au, (float,int)),\ + "au is of type: "+str(type(totest.au)) def test_instance_aursun(self): - self.assertIsInstance(totest.aursun, (float, int)) + assert isinstance(totest.aursun, (float,int)),\ + "aursun is of type: "+str(type(totest.aursun)) def test_instance_m_jupiter(self): - self.assertIsInstance(totest.m_jupiter, (float, int)) + assert isinstance(totest.m_jupiter, (float,int)),\ + "m_jupiter is of type: "+str(type(totest.m_jupiter)) def test_instance_r_jupiter(self): - self.assertIsInstance(totest.r_jupiter, (float, int)) + assert isinstance(totest.r_jupiter, (float,int)),\ + "r_jupiter is of type: "+str(type(totest.r_jupiter)) def test_instance_semimajor_axis_jupiter(self): - self.assertIsInstance(totest.semimajor_axis_jupiter, (float, int)) + assert isinstance(totest.semimajor_axis_jupiter, (float,int)),\ + "semimajor_axis_jupiter is of type: "+\ + str(type(totest.semimajor_axis_jupiter)) def test_instance_km2cm(self): - self.assertIsInstance(totest.km2cm, (float, int)) + assert isinstance(totest.km2cm, (float,int)),\ + "km2cm is of type: "+str(type(totest.km2cm)) def test_instance_day2sec(self): - self.assertIsInstance(totest.day2sec, (float, int)) + assert isinstance(totest.day2sec, (float,int)),\ + "day2sec is of type: "+str(type(totest.day2sec)) def test_instance_H_weight(self): - self.assertIsInstance(totest.H_weight, (float, int)) + assert isinstance(totest.H_weight, (float,int)),\ + "H_weight is of type: "+str(type(totest.H_weight)) def test_instance_He_weight(self): - self.assertIsInstance(totest.He_weight, (float, int)) + assert isinstance(totest.He_weight, (float,int)),\ + "He_weight is of type: "+str(type(totest.He_weight)) def test_instance_SNcheck_ERR(self): - self.assertIsInstance(totest.SNcheck_ERR, (float, int)) + assert isinstance(totest.SNcheck_ERR, (float,int)),\ + "SNcheck_ERR is of type: "+str(type(totest.SNcheck_ERR)) -class TestValues(unittest.TestCase): +class TestValues: # check that the values fit # use delta of last digit times 0.6 def test_value_pi(self): - value = 3.1415926535897932384626433832795028841971693993751 - self.assertAlmostEqual(totest.pi, value, delta=6e-50) + assert 3.1415926535897932384626433832795028841971693993751 ==\ + approx(totest.pi, abs=6e-50) def test_value_a2rad(self): - value = 1.7453292519943295e-2 - self.assertAlmostEqual(totest.a2rad, value, delta=6e-19) + assert 1.7453292519943295e-2 == approx(totest.a2rad, abs=6e-19) def test_value_rad2a(self): - value = 5.729577951308232e+1 - self.assertAlmostEqual(totest.rad2a, value, delta=6e-15) + assert 5.729577951308232e+1 == approx(totest.rad2a, abs=6e-15) def test_value_standard_cgrav(self): - value = 6.67428e-8 - self.assertAlmostEqual(totest.standard_cgrav, value, delta=6e-14) + assert 6.67428e-8 == approx(totest.standard_cgrav, abs=6e-14) def test_value_planck_h(self): - value = 6.62606896e-27 - self.assertAlmostEqual(totest.planck_h, value, delta=6e-36) + assert 6.62606896e-27 == approx(totest.planck_h, abs=6e-36) def test_value_hbar(self): - value = 1.05457163e-27 - self.assertAlmostEqual(totest.hbar, value, delta=6e-36) + assert 1.05457163e-27 == approx(totest.hbar, abs=6e-36) def test_value_qe(self): - value = 4.80320440e-10 - self.assertAlmostEqual(totest.qe, value, delta=6e-19) + assert 4.80320440e-10 == approx(totest.qe, abs=6e-19) def test_value_avo(self): - value = 6.02214129e+23 - self.assertAlmostEqual(totest.avo, value, delta=6e+14) + assert 6.02214129e+23 == approx(totest.avo, abs=6e+14) def test_value_clight(self): - value = 2.99792458e+10 - self.assertAlmostEqual(totest.clight, value, delta=6e+1) + assert 2.99792458e+10 == approx(totest.clight, abs=6e+1) def test_value_kerg(self): - value = 1.3806504e-16 - self.assertAlmostEqual(totest.kerg, value, delta=6e-24) + assert 1.3806504e-16 == approx(totest.kerg, abs=6e-24) def test_value_boltzm(self): - value = 1.3806504e-16 - self.assertAlmostEqual(totest.kerg, value, delta=6e-24) + assert 1.3806504e-16 == approx(totest.kerg, abs=6e-24) def test_value_cgas(self): - value = 8.314471780895016e+7 - self.assertAlmostEqual(totest.cgas, value, delta=6e-1) + assert 8.314471780895016e+7 == approx(totest.cgas, abs=6e-1) def test_value_kev(self): - value = 8.617385e-5 - self.assertAlmostEqual(totest.kev, value, delta=6e-12) + assert 8.617385e-5 == approx(totest.kev, abs=6e-12) def test_value_amu(self): - value = 1.6605389210321898e-24 - self.assertAlmostEqual(totest.amu, value, delta=6e-33) + assert 1.6605389210321898e-24 == approx(totest.amu, abs=6e-33) def test_value_mn(self): - value = 1.6749286e-24 - self.assertAlmostEqual(totest.mn, value, delta=6e-32) + assert 1.6749286e-24 == approx(totest.mn, abs=6e-32) def test_value_mp(self): - value = 1.6726231e-24 - self.assertAlmostEqual(totest.mp, value, delta=6e-32) + assert 1.6726231e-24 == approx(totest.mp, abs=6e-32) def test_value_me(self): - value = 9.1093826e-28 - self.assertAlmostEqual(totest.me, value, delta=6e-36) + assert 9.1093826e-28 == approx(totest.me, abs=6e-36) def test_value_rbohr(self): - value = 5.291771539809704e-9 - self.assertAlmostEqual(totest.rbohr, value, delta=6e-18) + assert 5.291771539809704e-9 == approx(totest.rbohr, abs=6e-18) def test_value_fine(self): - value = 7.297352926107705e-3 - self.assertAlmostEqual(totest.fine, value, delta=6e-11) + assert 7.297352926107705e-3 == approx(totest.fine, abs=6e-11) def test_value_hion(self): - value = 1.3605698140e+1 - self.assertAlmostEqual(totest.hion, value, delta=6e-10) + assert 1.3605698140e+1 == approx(totest.hion, abs=6e-10) def test_value_ev2erg(self): - value = 1.602176565e-12 - self.assertAlmostEqual(totest.ev2erg, value, delta=6e-22) + assert 1.602176565e-12 == approx(totest.ev2erg, abs=6e-22) def test_value_inversecm2erg(self): - value = 1.9864455003959037e-16 - self.assertAlmostEqual(totest.inversecm2erg, value, delta=6e-25) + assert 1.9864455003959037e-16 ==\ + approx(totest.inversecm2erg, abs=6e-25) def test_value_mev_to_ergs(self): - value = 1.6021765649999999e-6 - self.assertAlmostEqual(totest.mev_to_ergs, value, delta=6e-16) + assert 1.6021765649999999e-6 == approx(totest.mev_to_ergs, abs=6e-16) def test_value_mev_amu(self): - value = 9.648533645956869e+17 - self.assertAlmostEqual(totest.mev_amu, value, delta=6e+8) + assert 9.648533645956869e+17 == approx(totest.mev_amu, abs=6e+8) def test_value_Qconv(self): - value = 9.648533645956868e+17 - self.assertAlmostEqual(totest.Qconv, value, delta=6e+8) + assert 9.648533645956868e+17 == approx(totest.Qconv, abs=6e+8) def test_value_boltz_sigma(self): - value = 5.670373e-5 - self.assertAlmostEqual(totest.boltz_sigma, value, delta=6e-12) + assert 5.670373e-5 == approx(totest.boltz_sigma, abs=6e-12) def test_value_crad(self): - value = 7.565731356724124e-15 - self.assertAlmostEqual(totest.crad, value, delta=6e-22) + assert 7.565731356724124e-15 == approx(totest.crad, abs=6e-22) def test_value_ssol(self): - value = 5.670373e-5 - self.assertAlmostEqual(totest.ssol, value, delta=6e-12) + assert 5.670373e-5 == approx(totest.ssol, abs=6e-12) def test_value_asol(self): - value = 7.565731356724124e-15 - self.assertAlmostEqual(totest.asol, value, delta=6e-22) + assert 7.565731356724124e-15 == approx(totest.asol, abs=6e-22) def test_value_weinlam(self): - value = 2.897768496231288e-1 - self.assertAlmostEqual(totest.weinlam, value, delta=6e-9) + assert 2.897768496231288e-1 == approx(totest.weinlam, abs=6e-9) def test_value_weinfre(self): - value = 5.878932774535368e+10 - self.assertAlmostEqual(totest.weinfre, value, delta=6e+2) + assert 5.878932774535368e+10 == approx(totest.weinfre, abs=6e+2) def test_value_rhonuc(self): - value = 2.342e+14 - self.assertAlmostEqual(totest.rhonuc, value, delta=6e+10) + assert 2.342e+14 == approx(totest.rhonuc, abs=6e+10) def test_value_Zsun(self): - value = 1.42e-2 - self.assertAlmostEqual(totest.Zsun, value, delta=6e-5) + assert 1.42e-2 == approx(totest.Zsun, abs=6e-5) def test_value_msol(self): - value = 1.9892e+33 - self.assertAlmostEqual(totest.msol, value, delta=6e+28) + assert 1.9892e+33 == approx(totest.msol, abs=6e+28) def test_value_rsol(self): - value = 6.9598e+10 - self.assertAlmostEqual(totest.rsol, value, delta=6e+5) + assert 6.9598e+10 == approx(totest.rsol, abs=6e+5) def test_value_lsol(self): - value = 3.8418e+33 - self.assertAlmostEqual(totest.lsol, value, delta=6e+28) + assert 3.8418e+33 == approx(totest.lsol, abs=6e+28) def test_value_agesol(self): - value = 4.57e+9 - self.assertAlmostEqual(totest.agesol, value, delta=6e+6) + assert 4.57e+9 == approx(totest.agesol, abs=6e+6) def test_value_Msun(self): - value = 1.9892e+33 - self.assertAlmostEqual(totest.Msun, value, delta=6e+28) + assert 1.9892e+33 == approx(totest.Msun, abs=6e+28) def test_value_Rsun(self): - value = 6.9598e+10 - self.assertAlmostEqual(totest.Rsun, value, delta=6e+5) + assert 6.9598e+10 == approx(totest.Rsun, abs=6e+5) def test_value_Lsun(self): - value = 3.8418e+33 - self.assertAlmostEqual(totest.Lsun, value, delta=6e+28) + assert 3.8418e+33 == approx(totest.Lsun, abs=6e+28) def test_value_Msun33(self): - value = 1.9892 - self.assertAlmostEqual(totest.Msun33, value, delta=6e-5) + assert 1.9892 == approx(totest.Msun33, abs=6e-5) def test_value_Rsun11(self): - value = 6.9598e-1 - self.assertAlmostEqual(totest.Rsun11, value, delta=6e-6) + assert 6.9598e-1 == approx(totest.Rsun11, abs=6e-6) def test_value_Lsun33(self): - value = 3.8418 - self.assertAlmostEqual(totest.Lsun33, value, delta=6e-5) + assert 3.8418 == approx(totest.Lsun33, abs=6e-5) def test_value_ly(self): - value = 9.460528e+17 - self.assertAlmostEqual(totest.ly, value, delta=6e+10) + assert 9.460528e+17 == approx(totest.ly, abs=6e+10) def test_value_pc(self): - value = 3.0856770322224e+18 - self.assertAlmostEqual(totest.pc, value, delta=6e+11) + assert 3.0856770322224e+18 == approx(totest.pc, abs=6e+11) def test_value_secyer(self): - value = 3.1558149984e+7 - self.assertAlmostEqual(totest.secyer, value, delta=6e-4) + assert 3.1558149984e+7 == approx(totest.secyer, abs=6e-4) def test_value_dayyer(self): - value = 3.6525e+2 - self.assertAlmostEqual(totest.dayyer, value, delta=6e-3) + assert 3.6525e+2 == approx(totest.dayyer, abs=6e-3) def test_value_age_of_universe(self): - value = 1.38e+10 - self.assertAlmostEqual(totest.age_of_universe, value, delta=6e+7) + assert 1.38e+10 == approx(totest.age_of_universe, abs=6e+7) def test_value_Teffsol(self): - value = 5.7770e+3 - self.assertAlmostEqual(totest.Teffsol, value, delta=6e-2) + assert 5.7770e+3 == approx(totest.Teffsol, abs=6e-2) def test_value_loggsol(self): - value = 4.4378893534131256 - self.assertAlmostEqual(totest.loggsol, value, delta=6e-17) + assert 4.4378893534131256 == approx(totest.loggsol, abs=6e-17) def test_value_mbolsun(self): - value = 4.746 - self.assertAlmostEqual(totest.mbolsun, value, delta=6e-4) + assert 4.746 == approx(totest.mbolsun, abs=6e-4) def test_value_mbolsol(self): - value = 4.746 - self.assertAlmostEqual(totest.mbolsol, value, delta=6e-4) + assert 4.746 == approx(totest.mbolsol, abs=6e-4) def test_value_m_earth(self): - value = 5.9764e+27 - self.assertAlmostEqual(totest.m_earth, value, delta=6e+22) + assert 5.9764e+27 == approx(totest.m_earth, abs=6e+22) def test_value_r_earth(self): - value = 6.37e+8 - self.assertAlmostEqual(totest.r_earth, value, delta=6e+5) + assert 6.37e+8 == approx(totest.r_earth, abs=6e+5) def test_value_au(self): - value = 1.495978921e+13 - self.assertAlmostEqual(totest.au, value, delta=6e+3) + assert 1.495978921e+13 == approx(totest.au, abs=6e+3) def test_value_aursun(self): - value = 2.1495e+2 - self.assertAlmostEqual(totest.aursun, value, delta=6e-3) + assert 2.1495e+2 == approx(totest.aursun, abs=6e-3) def test_value_m_jupiter(self): - value = 1.8986e+30 - self.assertAlmostEqual(totest.m_jupiter, value, delta=6e+25) + assert 1.8986e+30 == approx(totest.m_jupiter, abs=6e+25) def test_value_r_jupiter(self): - value = 6.9911e+9 - self.assertAlmostEqual(totest.r_jupiter, value, delta=6e+4) + assert 6.9911e+9 == approx(totest.r_jupiter, abs=6e+4) def test_value_semimajor_axis_jupiter(self): - value = 7.7857e+13 - self.assertAlmostEqual(totest.semimajor_axis_jupiter, value, delta=6e+8) + assert 7.7857e+13 == approx(totest.semimajor_axis_jupiter, abs=6e+8) def test_value_km2cm(self): - value = 1.0e5 - self.assertAlmostEqual(totest.km2cm, value, delta=6e-16) + assert 1.0e5 == approx(totest.km2cm, abs=6e-16) def test_value_day2sec(self): - value = 8.64000e+4 - self.assertAlmostEqual(totest.day2sec, value, delta=6e-2) + assert 8.64000e+4 == approx(totest.day2sec, abs=6e-2) def test_value_H_weight(self): - value = 1.00782503207 - self.assertAlmostEqual(totest.H_weight, value, delta=6e-12) + assert 1.00782503207 == approx(totest.H_weight, abs=6e-12) def test_value_He_weight(self): - value = 4.002603254131 - self.assertAlmostEqual(totest.He_weight, value, delta=6e-13) + assert 4.002603254131 == approx(totest.He_weight, abs=6e-13) def test_value_SNcheck_ERR(self): - value = 1e-10 - self.assertAlmostEqual(totest.SNcheck_ERR, value, delta=6e-25) - - -if __name__ == "__main__": - unittest.main() + assert 1e-10 == approx(totest.SNcheck_ERR, abs=6e-31) diff --git a/posydon/unit_tests/utils/test_ignorereason.py b/posydon/unit_tests/utils/test_ignorereason.py index 2be81aa03..9ae562c5f 100644 --- a/posydon/unit_tests/utils/test_ignorereason.py +++ b/posydon/unit_tests/utils/test_ignorereason.py @@ -5,94 +5,95 @@ "Matthias Kruckow " ] -# import the unittest module and the module which will be tested -import unittest +# import the module which will be tested import posydon.utils.ignorereason as totest -# import other needed code for the tests +# import other needed code for the tests, which is not already imported in the +# module you like to test +from pytest import fixture, raises from inspect import isclass, isroutine -# define test classes -class TestElements(unittest.TestCase): +# define test classes collecting several test functions +class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): elements = ['IGNORE_REASONS_PRIORITY', 'IgnoreReason', '__authors__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] - self.assertListEqual(dir(totest), elements, - msg="There might be added or removed objects " - "without an update on the unit test.") + assert dir(totest) == elements, "There might be added or removed "\ + "objects without an update on the unit test." def test_instance_IGNORE_REASONS_PRIORITY(self): - self.assertIsInstance(totest.IGNORE_REASONS_PRIORITY, (list)) + assert isinstance(totest.IGNORE_REASONS_PRIORITY, (list)),\ + "IGNORE_REASONS_PRIORITY is of type: "+\ + str(type(totest.IGNORE_REASONS_PRIORITY)) def test_instance_IgnoreReason(self): - self.assertTrue(isclass(totest.IgnoreReason)) + assert isclass(totest.IgnoreReason) -class TestValues(unittest.TestCase): +class TestValues: # check that the values fit def test_value_IGNORE_REASONS_PRIORITY(self): - self.assertIn('ignored_no_history1', totest.IGNORE_REASONS_PRIORITY) - self.assertIn('ignored_no_binary_history', - totest.IGNORE_REASONS_PRIORITY) - self.assertIn('corrupted_history1', totest.IGNORE_REASONS_PRIORITY) - self.assertIn('corrupted_binary_history', - totest.IGNORE_REASONS_PRIORITY) - self.assertIn('corrupted_history2', totest.IGNORE_REASONS_PRIORITY) - self.assertIn('ignored_scrubbed_history', - totest.IGNORE_REASONS_PRIORITY) - self.assertIn('ignored_no_final_profile', - totest.IGNORE_REASONS_PRIORITY) - self.assertIn('ignored_no_RLO', totest.IGNORE_REASONS_PRIORITY) + v_last = None + for v in ['ignored_no_history1', 'ignored_no_binary_history', + 'corrupted_history1', 'corrupted_binary_history', + 'corrupted_history2', 'ignored_scrubbed_history', + 'ignored_no_final_profile', 'ignored_no_RLO']: + # check required values + assert v in totest.IGNORE_REASONS_PRIORITY + # check required order + if v_last is not None: + assert totest.IGNORE_REASONS_PRIORITY.index(v_last) <\ + totest.IGNORE_REASONS_PRIORITY.index(v) + v_last = v -class TestIgnoreReason(unittest.TestCase): +class TestIgnoreReason: # test the IgnoreReason class - def setUp(self): + @fixture + def NewInstance(self): # initialize an instance of the class for each test - self.IgnoreReason = totest.IgnoreReason() + return totest.IgnoreReason() - def test_init(self): - self.assertTrue(isroutine(self.IgnoreReason.__init__)) + def test_init(self, NewInstance): + assert isroutine(NewInstance.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.IgnoreReason, totest.IgnoreReason) - self.assertIsNone(self.IgnoreReason.reason) - self.assertIsNone(self.IgnoreReason.order) + assert isinstance(NewInstance, totest.IgnoreReason) + # strictly speaking, the following test does not test the variable + # asignments in the __init__, because they call the __setattr__ + assert NewInstance.reason is None + assert NewInstance.order is None - def test_bool(self): - self.assertTrue(isroutine(self.IgnoreReason.__bool__)) - self.assertFalse(self.IgnoreReason) - self.IgnoreReason.reason = totest.IGNORE_REASONS_PRIORITY[0] - self.assertTrue(self.IgnoreReason) + def test_bool(self, NewInstance): + assert isroutine(NewInstance.__bool__) + assert bool(NewInstance) == False + NewInstance.reason = totest.IGNORE_REASONS_PRIORITY[0] + assert bool(NewInstance) - def test_setattr(self): - self.assertTrue(isroutine(self.IgnoreReason.__setattr__)) + def test_setattr(self, NewInstance): + assert isroutine(NewInstance.__setattr__) # try to set order: shouldn't change anything - self.IgnoreReason.order = 0 - self.assertIsNone(self.IgnoreReason.reason) - self.assertIsNone(self.IgnoreReason.order) + NewInstance.order = 0 + assert NewInstance.reason is None + assert NewInstance.order is None # set all reasons in decreasing order and compare it for r in reversed(totest.IGNORE_REASONS_PRIORITY): o = totest.IGNORE_REASONS_PRIORITY.index(r) - self.IgnoreReason.reason = r - self.assertEqual(self.IgnoreReason.order, o) - self.assertEqual(self.IgnoreReason.reason, r) + NewInstance.reason = r + assert NewInstance.order == o + assert NewInstance.reason == r # try to set reason of lowest priority: reason of higher pririty will # be kept - self.IgnoreReason.reason = totest.IGNORE_REASONS_PRIORITY[-1] - self.assertEqual(self.IgnoreReason.order, o) - self.assertEqual(self.IgnoreReason.reason, r) + NewInstance.reason = totest.IGNORE_REASONS_PRIORITY[-1] + assert NewInstance.order == o + assert NewInstance.reason == r # unset the reason: set back to None - self.IgnoreReason.reason = None - self.assertIsNone(self.IgnoreReason.reason) - self.assertIsNone(self.IgnoreReason.order) + NewInstance.reason = None + assert NewInstance.reason is None + assert NewInstance.order is None # try error on non existing reason - self.assertNotIn('', totest.IGNORE_REASONS_PRIORITY) - with self.assertRaises(ValueError): - self.IgnoreReason.reason = '' - - -if __name__ == "__main__": - unittest.main() + assert '' not in totest.IGNORE_REASONS_PRIORITY + with raises(ValueError): + NewInstance.reason = '' diff --git a/posydon/unit_tests/utils/test_limits_thresholds.py b/posydon/unit_tests/utils/test_limits_thresholds.py index b32785d10..4feeb9d4a 100644 --- a/posydon/unit_tests/utils/test_limits_thresholds.py +++ b/posydon/unit_tests/utils/test_limits_thresholds.py @@ -5,15 +5,15 @@ "Matthias Kruckow " ] -# import the unittest module and the module which will be tested -import unittest +# import the module which will be tested import posydon.utils.limits_thresholds as totest -# import other needed code for the tests -# not needed +# import other needed code for the tests, which is not already imported in the +# module you like to test -# define test classes -class TestElements(unittest.TestCase): + +# define test classes collecting several test functions +class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): elements = ['LG_MTRANSFER_RATE_THRESHOLD', 'LOG10_BURNING_THRESHOLD', @@ -27,52 +27,61 @@ def test_dir(self): 'THRESHOLD_NUCLEAR_LUMINOSITY', '__authors__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'np'] - self.assertListEqual(dir(totest), elements, - msg="There might be added or removed objects " - "without an update on the unit test.") + assert dir(totest) == elements, "There might be added or removed "\ + "objects without an update on the unit test." def test_instance_RL_RELATIVE_OVERFLOW_THRESHOLD(self): - self.assertIsInstance(totest.RL_RELATIVE_OVERFLOW_THRESHOLD, - (float, int)) + assert isinstance(totest.RL_RELATIVE_OVERFLOW_THRESHOLD, (float,int)),\ + "RL_RELATIVE_OVERFLOW_THRESHOLD is of type: "+\ + str(type(totest.RL_RELATIVE_OVERFLOW_THRESHOLD)) def test_instance_LG_MTRANSFER_RATE_THRESHOLD(self): - self.assertIsInstance(totest.LG_MTRANSFER_RATE_THRESHOLD, - (float, int)) + assert isinstance(totest.LG_MTRANSFER_RATE_THRESHOLD, (float,int)),\ + "LG_MTRANSFER_RATE_THRESHOLD is of type: "+\ + str(type(totest.LG_MTRANSFER_RATE_THRESHOLD)) def test_instance_THRESHOLD_CENTRAL_ABUNDANCE(self): - self.assertIsInstance(totest.THRESHOLD_CENTRAL_ABUNDANCE, - (float, int)) + assert isinstance(totest.THRESHOLD_CENTRAL_ABUNDANCE, (float,int)),\ + "THRESHOLD_CENTRAL_ABUNDANCE is of type: "+\ + str(type(totest.THRESHOLD_CENTRAL_ABUNDANCE)) def test_instance_THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C(self): - self.assertIsInstance(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C, - (float, int)) + assert isinstance(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C,\ + (float,int)), "THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C is of "\ + "type: "+str(type(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C)) def test_instance_THRESHOLD_HE_NAKED_ABUNDANCE(self): - self.assertIsInstance(totest.THRESHOLD_HE_NAKED_ABUNDANCE, - (float, int)) + assert isinstance(totest.THRESHOLD_HE_NAKED_ABUNDANCE, (float,int)),\ + "THRESHOLD_HE_NAKED_ABUNDANCE is of type: "+\ + str(type(totest.THRESHOLD_HE_NAKED_ABUNDANCE)) def test_instance_THRESHOLD_NUCLEAR_LUMINOSITY(self): - self.assertIsInstance(totest.THRESHOLD_NUCLEAR_LUMINOSITY, - (float, int)) + assert isinstance(totest.THRESHOLD_NUCLEAR_LUMINOSITY, (float,int)),\ + "THRESHOLD_NUCLEAR_LUMINOSITY is of type: "+\ + str(type(totest.THRESHOLD_NUCLEAR_LUMINOSITY)) def test_instance_REL_LOG10_BURNING_THRESHOLD(self): - self.assertIsInstance(totest.REL_LOG10_BURNING_THRESHOLD, - (float, int)) + assert isinstance(totest.REL_LOG10_BURNING_THRESHOLD, (float,int)),\ + "REL_LOG10_BURNING_THRESHOLD is of type: "+\ + str(type(totest.REL_LOG10_BURNING_THRESHOLD)) def test_instance_LOG10_BURNING_THRESHOLD(self): - self.assertIsInstance(totest.LOG10_BURNING_THRESHOLD, - (float, int)) + assert isinstance(totest.LOG10_BURNING_THRESHOLD, (float,int)),\ + "LOG10_BURNING_THRESHOLD is of type: "+\ + str(type(totest.LOG10_BURNING_THRESHOLD)) def test_instance_STATE_NS_STARMASS_UPPER_LIMIT(self): - self.assertIsInstance(totest.STATE_NS_STARMASS_UPPER_LIMIT, - (float, int)) + assert isinstance(totest.STATE_NS_STARMASS_UPPER_LIMIT, (float,int)),\ + "STATE_NS_STARMASS_UPPER_LIMIT is of type: "+\ + str(type(totest.STATE_NS_STARMASS_UPPER_LIMIT)) def test_instance_NEUTRINO_MASS_LOSS_UPPER_LIMIT(self): - self.assertIsInstance(totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT, - (float, int)) + assert isinstance(totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT, (float,int)),\ + "NEUTRINO_MASS_LOSS_UPPER_LIMIT is of type: "+\ + str(type(totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT)) -class TestLimits(unittest.TestCase): +class TestLimits: # check for validity ranges # def test_limits_RL_RELATIVE_OVERFLOW_THRESHOLD(self): # has no limits @@ -82,41 +91,38 @@ class TestLimits(unittest.TestCase): def test_limits_THRESHOLD_CENTRAL_ABUNDANCE(self): # an abundance should be in [0,1] - self.assertGreaterEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE, 0.0) - self.assertLessEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE, 1.0) + assert totest.THRESHOLD_CENTRAL_ABUNDANCE >= 0.0 + assert totest.THRESHOLD_CENTRAL_ABUNDANCE <= 1.0 def test_limits_THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C(self): # an abundance should be in [0,1] - self.assertGreaterEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C, 0.0) - self.assertLessEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C, 1.0) + assert totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C >= 0.0 + assert totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C <= 1.0 # it should be limited by THRESHOLD_CENTRAL_ABUNDANCE - self.assertGreaterEqual(totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C, totest.THRESHOLD_CENTRAL_ABUNDANCE) + assert totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C >=\ + totest.THRESHOLD_CENTRAL_ABUNDANCE def test_limits_THRESHOLD_HE_NAKED_ABUNDANCE(self): # an abundance should be in [0,1] - self.assertGreaterEqual(totest.THRESHOLD_HE_NAKED_ABUNDANCE, 0.0) - self.assertLessEqual(totest.THRESHOLD_HE_NAKED_ABUNDANCE, 1.0) + assert totest.THRESHOLD_HE_NAKED_ABUNDANCE >= 0.0 + assert totest.THRESHOLD_HE_NAKED_ABUNDANCE <= 1.0 def test_limits_THRESHOLD_NUCLEAR_LUMINOSITY(self): # an fraction should be in [0,1] - self.assertGreaterEqual(totest.THRESHOLD_NUCLEAR_LUMINOSITY, 0.0) - self.assertLessEqual(totest.THRESHOLD_NUCLEAR_LUMINOSITY, 1.0) + assert totest.THRESHOLD_NUCLEAR_LUMINOSITY >= 0.0 + assert totest.THRESHOLD_NUCLEAR_LUMINOSITY <= 1.0 def test_limits_REL_LOG10_BURNING_THRESHOLD(self): - # the log of a fraction should be <0 - self.assertLessEqual(totest.REL_LOG10_BURNING_THRESHOLD, 0.0) + # the log of a fraction should be <=0 + assert totest.REL_LOG10_BURNING_THRESHOLD <= 0.0 # def test_limits_LOG10_BURNING_THRESHOLD(self): # has no limits def test_limits_STATE_NS_STARMASS_UPPER_LIMIT(self): # a mass should be >0 - self.assertGreater(totest.STATE_NS_STARMASS_UPPER_LIMIT, 0.0) + assert totest.STATE_NS_STARMASS_UPPER_LIMIT > 0.0 def test_limits_NEUTRINO_MASS_LOSS_UPPER_LIMIT(self): # a mass limit should be >=0 - self.assertGreaterEqual(totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT, 0.0) - - -if __name__ == "__main__": - unittest.main() + assert totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT >= 0.0 diff --git a/posydon/unit_tests/utils/test_posydonerror.py b/posydon/unit_tests/utils/test_posydonerror.py index 7bf426c95..eef940f3c 100644 --- a/posydon/unit_tests/utils/test_posydonerror.py +++ b/posydon/unit_tests/utils/test_posydonerror.py @@ -5,15 +5,32 @@ "Matthias Kruckow " ] -# import the unittest module and the module which will be tested +# import the module which will be tested import unittest import posydon.utils.posydonerror as totest -# import other needed code for the tests +# import other needed code for the tests, which is not already imported in the +# module you like to test +from pytest import fixture, raises from inspect import isclass, isroutine -# define test classes -class TestElements(unittest.TestCase): +@fixture +def artificial_object(): + # create a dict as test object + return {'Test': 'object'} + +@fixture +def BinaryStar(): + # initialize a BinaryStar instance, which is a required argument + return totest.BinaryStar() + +@fixture +def SingleStar(): + # initialize a SingleStar instance, which is a required argument + return totest.SingleStar() + +# define test classes collecting several test functions +class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): elements = ['BinaryStar', 'FlowError', 'GridError', 'MatchingError', @@ -22,113 +39,144 @@ def test_dir(self): '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'copy', 'initial_condition_message'] - self.assertListEqual(dir(totest), elements, - msg="There might be added or removed objects " - "without an update on the unit test.") + assert dir(totest) == elements, "There might be added or removed "\ + "objects without an update on the unit test." def test_instance_POSYDONError(self): - self.assertTrue(isclass(totest.POSYDONError)) - self.assertTrue(issubclass(totest.POSYDONError, Exception)) + assert isclass(totest.POSYDONError) + assert issubclass(totest.POSYDONError, Exception) + with raises(totest.POSYDONError, match="Test"): + raise totest.POSYDONError("Test") def test_instance_FlowError(self): - self.assertTrue(isclass(totest.FlowError)) - self.assertTrue(issubclass(totest.FlowError, totest.POSYDONError)) + assert isclass(totest.FlowError) + assert issubclass(totest.FlowError, totest.POSYDONError) + with raises(totest.FlowError, match="Test"): + raise totest.FlowError("Test") def test_instance_GridError(self): - self.assertTrue(isclass(totest.GridError)) - self.assertTrue(issubclass(totest.GridError, totest.POSYDONError)) + assert isclass(totest.GridError) + assert issubclass(totest.GridError, totest.POSYDONError) + with raises(totest.GridError, match="Test"): + raise totest.GridError("Test") def test_instance_MatchingError(self): - self.assertTrue(isclass(totest.MatchingError)) - self.assertTrue(issubclass(totest.MatchingError, totest.POSYDONError)) + assert isclass(totest.MatchingError) + assert issubclass(totest.MatchingError, totest.POSYDONError) + with raises(totest.MatchingError, match="Test"): + raise totest.MatchingError("Test") def test_instance_ModelError(self): - self.assertTrue(isclass(totest.ModelError)) - self.assertTrue(issubclass(totest.ModelError, totest.POSYDONError)) + assert isclass(totest.ModelError) + assert issubclass(totest.ModelError, totest.POSYDONError) + with raises(totest.ModelError, match="Test"): + raise totest.ModelError("Test") def test_instance_NumericalError(self): - self.assertTrue(isclass(totest.NumericalError)) - self.assertTrue(issubclass(totest.NumericalError, totest.POSYDONError)) + assert isclass(totest.NumericalError) + assert issubclass(totest.NumericalError, totest.POSYDONError) + with raises(totest.NumericalError, match="Test"): + raise totest.NumericalError("Test") def test_instance_initial_condition_message(self): - self.assertTrue(isroutine(totest.initial_condition_message)) + assert isroutine(totest.initial_condition_message) -class TestFunctions(unittest.TestCase): +class TestFunctions: # test functions - def setUp(self): - # initialize a BinaryStar instance, which is a required argument - self.BinaryStar = totest.BinaryStar() - - def test_initial_condition_message(self): - test_object = {'Test': 'object'} - with self.assertRaises(TypeError): - message = totest.initial_condition_message(binary=test_object) - self.assertIn("Failed Binary Initial Conditions", - totest.initial_condition_message(binary=self.BinaryStar)) - with self.assertRaises(TypeError): - message = totest.initial_condition_message(binary=test_object, + def test_initial_condition_message(self, BinaryStar, artificial_object): + with raises(TypeError, match="The binary must be a BinaryStar object"): + message = totest.initial_condition_message(binary=\ + artificial_object) + assert "Failed Binary Initial Conditions" in\ + totest.initial_condition_message(binary=BinaryStar) + with raises(TypeError, match="is not iterable"): + message = totest.initial_condition_message(binary=BinaryStar, ini_params=1) - with self.assertRaises(TypeError): - message = totest.initial_condition_message(binary=test_object, + with raises(TypeError, match="can only concatenate str"): + message = totest.initial_condition_message(binary=BinaryStar, ini_params=[1,2]) - self.assertEqual("a: 1\nb: 2\n", - totest.initial_condition_message(binary=self.BinaryStar, - ini_params=["a: 1\n", "b: 2\n"])) + assert "a: 1\nb: 2\n" == totest.initial_condition_message(\ + binary=BinaryStar, ini_params=["a: 1\n", "b: 2\n"]) -class TestPOSYDONError(unittest.TestCase): +class TestPOSYDONError: # test the POSYDONError class - def setUp(self): - # initialize an instance of the class for each test - self.POSYDONError = totest.POSYDONError("test message on posittion") - - def test_init(self): - self.assertTrue(isroutine(self.POSYDONError.__init__)) + @fixture + def POSYDONError(self): + # initialize an instance of the class with defaults + return totest.POSYDONError() + + @fixture + def POSYDONError_position(self): + # initialize an instance of the class with a positional argument + return totest.POSYDONError("test message on position") + + @fixture + def POSYDONError_key(self): + # initialize an instance of the class with a message via key + return totest.POSYDONError(message="test message with key") + + @fixture + def POSYDONError_object(self, artificial_object): + # initialize an instance of the class with an artifical object in a + # list via key + return totest.POSYDONError(objects=[artificial_object]) + + @fixture + def POSYDONError_SingleStar(self, SingleStar): + # initialize an instance of the class with a SingleStar object + return totest.POSYDONError(objects=SingleStar) + + @fixture + def POSYDONError_BinaryStar(self, BinaryStar): + # initialize an instance of the class with a BinaryStar object + return totest.POSYDONError(objects=BinaryStar) + + @fixture + def POSYDONError_List(self, artificial_object, SingleStar, BinaryStar): + # initialize an instance of the class with a list of objects + return totest.POSYDONError(objects=[artificial_object, SingleStar,\ + BinaryStar]) + + def test_init(self, POSYDONError, POSYDONError_position, POSYDONError_key,\ + POSYDONError_object, artificial_object): + assert isroutine(POSYDONError.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.POSYDONError, totest.POSYDONError) - self.assertEqual(self.POSYDONError.message, - "test message on posittion") - self.assertIsNone(self.POSYDONError.objects) + assert isinstance(POSYDONError, totest.POSYDONError) # check defaults - error_object = totest.POSYDONError() - self.assertEqual(error_object.message, "") - self.assertIsNone(error_object.objects) - # test a passed message + assert POSYDONError.message == "" + assert POSYDONError.objects is None + # test a passed message via positional argument + assert POSYDONError_position.message == "test message on position" + assert POSYDONError_position.objects is None + # test a passed message via key error_object = totest.POSYDONError(message="test message with key") - self.assertEqual(error_object.message, "test message with key") - self.assertIsNone(error_object.objects) + assert POSYDONError_key.message == "test message with key" + assert POSYDONError_key.objects is None # test a passed object - test_object = {'Test': 'object'} - error_object = totest.POSYDONError(objects=[test_object]) - self.assertEqual(error_object.message, "") - self.assertListEqual(error_object.objects, [test_object]) + assert POSYDONError_object.message == "" + assert POSYDONError_object.objects == [artificial_object] # test requests on input parameters - with self.assertRaises(TypeError): - error_object = totest.POSYDONError(message=test_object) - with self.assertRaises(TypeError): - error_object = totest.POSYDONError(objects=test_object) + with raises(TypeError, match="message must be a string"): + error_object = totest.POSYDONError(message=artificial_object) + with raises(TypeError, match="objects must be None, a list, a "+\ + "SingleStar object, or a BinaryStar object"): + error_object = totest.POSYDONError(objects=artificial_object) - def test_str(self): - self.assertTrue(isroutine(self.POSYDONError.__str__)) - self.assertEqual(str(self.POSYDONError), "\ntest message on posittion") + def test_str(self, POSYDONError_position, POSYDONError_object, POSYDONError_SingleStar, POSYDONError_BinaryStar, POSYDONError_List): + assert isroutine(POSYDONError_position.__str__) + assert str(POSYDONError_position) == "\ntest message on position" # test passed objects - test_object1 = {'Test': 'object'} - error_object = totest.POSYDONError(objects=[test_object1]) - self.assertEqual(str(error_object), "\n") - test_object2 = totest.SingleStar() - error_object = totest.POSYDONError(objects=test_object2) - self.assertIn("OBJECT #()", str(error_object)) - test_object3 = totest.BinaryStar() - error_object = totest.POSYDONError(objects=test_object3) - self.assertIn("OBJECT #()", str(error_object)) - error_object = totest.POSYDONError(objects=[test_object1, test_object2, test_object3]) - self.assertNotIn("OBJECT #1", str(error_object)) - self.assertIn("OBJECT #2 ()", str(error_object)) - self.assertIn("OBJECT #3 ()", str(error_object)) - - -if __name__ == "__main__": - unittest.main() + assert str(POSYDONError_object) == "\n" + assert "OBJECT #()"\ + in str(POSYDONError_SingleStar) + assert "OBJECT #()"\ + in str(POSYDONError_BinaryStar) + assert "OBJECT #1" not in str(POSYDONError_List) + assert "OBJECT #2 ()" in str(POSYDONError_List) + assert "OBJECT #3 ()" in str(POSYDONError_List) diff --git a/posydon/utils/posydonerror.py b/posydon/utils/posydonerror.py index 4365ce79f..31a373b5a 100644 --- a/posydon/utils/posydonerror.py +++ b/posydon/utils/posydonerror.py @@ -34,7 +34,7 @@ def __init__(self, message="", objects=None): raise TypeError("The error message must be a string.") if ((objects is not None) and (not isinstance(objects, (list, SingleStar, BinaryStar)))): - raise TypeError("The error message must be None, a list, a " + raise TypeError("The error objects must be None, a list, a " "SingleStar object, or a BinaryStar object.") self.message = message # copy the objects: we must know their state at the moment of the error @@ -51,7 +51,7 @@ def __str__(self): result += f"\n\nOBJECT #{i+1} ({type(obj)}):\n{str(obj)}" elif isinstance(self.objects, (BinaryStar, SingleStar)): result += f"\n\nOBJECT #({type(self.objects)}):\n{str(self.objects)}" - else: + else: # pragma: no cover pass return result + '\n'+ super().__str__() From d9a6ee93715fa2c5ddfa349ed153513c037c503a Mon Sep 17 00:00:00 2001 From: mkruckow Date: Thu, 26 Sep 2024 09:56:23 +0200 Subject: [PATCH 12/34] account for the comments of Max --- posydon/unit_tests/README.md | 4 +- posydon/unit_tests/utils/test_constants.py | 4 +- posydon/unit_tests/utils/test_ignorereason.py | 9 ++-- .../utils/test_limits_thresholds.py | 48 +++++++++++++------ posydon/unit_tests/utils/test_posydonerror.py | 4 +- 5 files changed, 45 insertions(+), 24 deletions(-) diff --git a/posydon/unit_tests/README.md b/posydon/unit_tests/README.md index 447a60825..d63e89dc4 100644 --- a/posydon/unit_tests/README.md +++ b/posydon/unit_tests/README.md @@ -58,7 +58,7 @@ Beside the existence and the type of a variable, we should verify the integrity ##### Check functions -Function in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions need variables may [use fixtures](#using-fixtures). +Function in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions need variables, you may [use fixtures](#using-fixtures). Functions may include prints statements. In such cases it is useful to redirect the data stream going there into a variable to be able to validate the output (there is a similar context manager to redirect `stderr`). Here an example code: @@ -85,7 +85,7 @@ Pytest has the [context manager `pytest.raises`](https://docs.pytest.org/en/stab ### Check that it can fail -Whenever you wrote a test, it should succeed on existing code. But at the other hand you should make a test that it can fail, otherwise the test won't do its work. +Whenever you write a test, it should succeed on the existing code. On the other hand, you should also make a test where you expect it to fail, otherwise, the test won't do its job. ## How to update a unit test diff --git a/posydon/unit_tests/utils/test_constants.py b/posydon/unit_tests/utils/test_constants.py index 07488f619..3db818b0e 100644 --- a/posydon/unit_tests/utils/test_constants.py +++ b/posydon/unit_tests/utils/test_constants.py @@ -30,8 +30,8 @@ def test_dir(self): 'planck_h', 'qe', 'r_earth', 'r_jupiter', 'rad2a', 'rbohr', 'rhonuc', 'rsol', 'secyer', 'semimajor_axis_jupiter', 'ssol', 'standard_cgrav', 'weinfre', 'weinlam'] - assert dir(totest) == elements, "There might be added or removed "\ - "objects without an update on the unit test." + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." def test_instance_pi(self): assert isinstance(totest.pi, (float,int)),\ diff --git a/posydon/unit_tests/utils/test_ignorereason.py b/posydon/unit_tests/utils/test_ignorereason.py index 9ae562c5f..603f1732b 100644 --- a/posydon/unit_tests/utils/test_ignorereason.py +++ b/posydon/unit_tests/utils/test_ignorereason.py @@ -20,8 +20,8 @@ def test_dir(self): elements = ['IGNORE_REASONS_PRIORITY', 'IgnoreReason', '__authors__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] - assert dir(totest) == elements, "There might be added or removed "\ - "objects without an update on the unit test." + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." def test_instance_IGNORE_REASONS_PRIORITY(self): assert isinstance(totest.IGNORE_REASONS_PRIORITY, (list)),\ @@ -41,11 +41,12 @@ def test_value_IGNORE_REASONS_PRIORITY(self): 'corrupted_history2', 'ignored_scrubbed_history', 'ignored_no_final_profile', 'ignored_no_RLO']: # check required values - assert v in totest.IGNORE_REASONS_PRIORITY + assert v in totest.IGNORE_REASONS_PRIORITY, "missing entry" # check required order if v_last is not None: assert totest.IGNORE_REASONS_PRIORITY.index(v_last) <\ - totest.IGNORE_REASONS_PRIORITY.index(v) + totest.IGNORE_REASONS_PRIORITY.index(v),\ + f"the priority order has changed for: {v_last} or {v}" v_last = v diff --git a/posydon/unit_tests/utils/test_limits_thresholds.py b/posydon/unit_tests/utils/test_limits_thresholds.py index 4feeb9d4a..625672357 100644 --- a/posydon/unit_tests/utils/test_limits_thresholds.py +++ b/posydon/unit_tests/utils/test_limits_thresholds.py @@ -27,8 +27,8 @@ def test_dir(self): 'THRESHOLD_NUCLEAR_LUMINOSITY', '__authors__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'np'] - assert dir(totest) == elements, "There might be added or removed "\ - "objects without an update on the unit test." + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." def test_instance_RL_RELATIVE_OVERFLOW_THRESHOLD(self): assert isinstance(totest.RL_RELATIVE_OVERFLOW_THRESHOLD, (float,int)),\ @@ -91,38 +91,58 @@ class TestLimits: def test_limits_THRESHOLD_CENTRAL_ABUNDANCE(self): # an abundance should be in [0,1] - assert totest.THRESHOLD_CENTRAL_ABUNDANCE >= 0.0 - assert totest.THRESHOLD_CENTRAL_ABUNDANCE <= 1.0 + assert totest.THRESHOLD_CENTRAL_ABUNDANCE >= 0.0,\ + "THRESHOLD_CENTRAL_ABUNDANCE should be in the range [0,1], "+\ + "but it is below" + assert totest.THRESHOLD_CENTRAL_ABUNDANCE <= 1.0,\ + "THRESHOLD_CENTRAL_ABUNDANCE should be in the range [0,1], "+\ + "but it is above" def test_limits_THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C(self): # an abundance should be in [0,1] - assert totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C >= 0.0 - assert totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C <= 1.0 + assert totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C >= 0.0,\ + "THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C should be in the range "+\ + "[0,1], but it is below" + assert totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C <= 1.0,\ + "THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C should be in the range "+\ + "[0,1], but it is above" # it should be limited by THRESHOLD_CENTRAL_ABUNDANCE assert totest.THRESHOLD_CENTRAL_ABUNDANCE_LOOSE_C >=\ - totest.THRESHOLD_CENTRAL_ABUNDANCE + totest.THRESHOLD_CENTRAL_ABUNDANCE,\ + "the loose condition should be less strict" def test_limits_THRESHOLD_HE_NAKED_ABUNDANCE(self): # an abundance should be in [0,1] - assert totest.THRESHOLD_HE_NAKED_ABUNDANCE >= 0.0 - assert totest.THRESHOLD_HE_NAKED_ABUNDANCE <= 1.0 + assert totest.THRESHOLD_HE_NAKED_ABUNDANCE >= 0.0,\ + "THRESHOLD_HE_NAKED_ABUNDANCE should be in the range [0,1], "+\ + "but it is below" + assert totest.THRESHOLD_HE_NAKED_ABUNDANCE <= 1.0,\ + "THRESHOLD_HE_NAKED_ABUNDANCE should be in the range [0,1], "+\ + "but it is above" def test_limits_THRESHOLD_NUCLEAR_LUMINOSITY(self): # an fraction should be in [0,1] - assert totest.THRESHOLD_NUCLEAR_LUMINOSITY >= 0.0 - assert totest.THRESHOLD_NUCLEAR_LUMINOSITY <= 1.0 + assert totest.THRESHOLD_NUCLEAR_LUMINOSITY >= 0.0,\ + "THRESHOLD_NUCLEAR_LUMINOSITY should be in the range [0,1], "+\ + "but it is below" + assert totest.THRESHOLD_NUCLEAR_LUMINOSITY <= 1.0,\ + "THRESHOLD_NUCLEAR_LUMINOSITY should be in the range [0,1], "+\ + "but it is above" def test_limits_REL_LOG10_BURNING_THRESHOLD(self): # the log of a fraction should be <=0 - assert totest.REL_LOG10_BURNING_THRESHOLD <= 0.0 + assert totest.REL_LOG10_BURNING_THRESHOLD <= 0.0, "a fraction should"+\ + " be in the range [0,1], hence the log of it can't be positive" # def test_limits_LOG10_BURNING_THRESHOLD(self): # has no limits def test_limits_STATE_NS_STARMASS_UPPER_LIMIT(self): # a mass should be >0 - assert totest.STATE_NS_STARMASS_UPPER_LIMIT > 0.0 + assert totest.STATE_NS_STARMASS_UPPER_LIMIT > 0.0,\ + "a mass has to be positve" def test_limits_NEUTRINO_MASS_LOSS_UPPER_LIMIT(self): # a mass limit should be >=0 - assert totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT >= 0.0 + assert totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT >= 0.0,\ + "there shouldn't be a mass gain" diff --git a/posydon/unit_tests/utils/test_posydonerror.py b/posydon/unit_tests/utils/test_posydonerror.py index eef940f3c..a57d820e1 100644 --- a/posydon/unit_tests/utils/test_posydonerror.py +++ b/posydon/unit_tests/utils/test_posydonerror.py @@ -39,8 +39,8 @@ def test_dir(self): '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'copy', 'initial_condition_message'] - assert dir(totest) == elements, "There might be added or removed "\ - "objects without an update on the unit test." + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." def test_instance_POSYDONError(self): assert isclass(totest.POSYDONError) From b68742f77e2f7c777668694cfbf529ed9149cd72 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Wed, 2 Oct 2024 18:14:29 +0200 Subject: [PATCH 13/34] transfer test_posydonwarning.py to pytest --- posydon/unit_tests/README.md | 40 +- posydon/unit_tests/utils/test_ignorereason.py | 4 +- posydon/unit_tests/utils/test_posydonerror.py | 3 +- .../unit_tests/utils/test_posydonwarning.py | 883 ++++++++++-------- posydon/utils/posydonwarning.py | 22 +- 5 files changed, 546 insertions(+), 406 deletions(-) diff --git a/posydon/unit_tests/README.md b/posydon/unit_tests/README.md index d63e89dc4..7ce72f68a 100644 --- a/posydon/unit_tests/README.md +++ b/posydon/unit_tests/README.md @@ -60,18 +60,34 @@ Beside the existence and the type of a variable, we should verify the integrity Function in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions need variables, you may [use fixtures](#using-fixtures). -Functions may include prints statements. In such cases it is useful to redirect the data stream going there into a variable to be able to validate the output (there is a similar context manager to redirect `stderr`). Here an example code: +Functions may include prints statements. To capture outputs to `stdout` and `stderr` pytest has global defined fixtures `capsys` (and `capsysbinary` for bytes data instead of usual text; addtionally, there are `capfd` and `capfdbinary` to capture the file descriptors `1` and `2`). - from io import StringIO - from contextlib import redirect_stdout - ... - with redirect_stdout(StringIO()) as print_out: - totest.function_with_print_statements() - self.assertEqual("Text of first print statement.\nText of second print statement.\n", print_out.getvalue()) +To access the captured content you simply call the `readouterr` function of the fixture. It will return a `(out, err)` namedtuple. Here an example how to get the stdout content, captured so far: + + capsys.readouterr().out + +It should be noted, that the call of `readouterr` will clear the buffer, hence if you like to get both `out` and `err` at the same time, you need to store the namedtuple and access the components from the stored version, e.g. + + captured_output = capsys.readouterr() + captured_output.out + captured_output.err + +In case you like to access what is captured to far but keep it in the buffer, you'd need to reprint the part you read, e.g. + + captured_output = capsys.readouterr() + captured_output.out + print(captured_output.out) + +Using those fixtures as arguments to a function will capture all outputs of that function. To exclude some parts from getting its output captured you need to put that into the `disabled` context of the used fixture. It should be noted, that only the capturing is disabled, but the fixture object is still available, e.g. + + with capsys.disabled(): + print(capsys.readouterr().out) + +will print the collected prints to stdout, at this moment all together (and clears the buffer). ##### Check classes -Each class inside a module should be get its components checked like a module itself. *I suggest to have a test class for each class in the tested module and the test of each class function should get an own test function.* Again, `setUp` and/or `tearDown` functions should be used to ensure that all tests run under the same conditions. +Each class inside a module should be get its components checked like a module itself. *I suggest to have a test class for each class in the tested module and the test of each class function should get an own test function.* Again, [use fixtures](#using-fixtures) can be used to ensure that all tests run under the same conditions. #### Using fixtures @@ -79,10 +95,18 @@ You can define [fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html @pytest.fixture +Fixtures replace the `setUp` and `tearDown`. To use a fixture to prepare something before a test, you can simply write it as a function and the variable will contain the returned value. + +To do with cleaning things up after a test, instead of having a final return, you separate setUp and tearDown with a yield statement, which ensure that all before is executed when the fixture is requested and the stuff after when it get deleted. For chains of fixtures it should be noted, that the clean up happens in the reverse order to the creation, because the innermost fixture will get deleted first. + #### Catching raised errors Pytest has the [context manager `pytest.raises`](https://docs.pytest.org/en/stable/reference/reference.html#pytest-raises) to catch raised errors. You use it like other context managers via a `with` statement. Beside the expected exception, you can specify a `match`, which will be checked against the error message. The context object can be used to check for more details of the raised error. +#### Catching warnings + +Usually, pytest will catch all warnings and print them at the end of all tests. If your test will cause a warning which you don't like to have displayed, you can filter the warnings caught by pytest. To filter all warnings in a function or class you can decorate it with a filter, e.g. `@pytest.mark.filterwarnings("ignore:WARNINGTEXT")`. There are more things you can do on [warnings in pytest](https://docs.pytest.org/en/stable/how-to/capture-warnings.html), but you should use that only were needed. But you should be careful with the pytest warning catching, because it overwrites some parts of the python warnings, which even interferes badly with our POSYDON warnings (especially the filter changes). + ### Check that it can fail Whenever you write a test, it should succeed on the existing code. On the other hand, you should also make a test where you expect it to fail, otherwise, the test won't do its job. diff --git a/posydon/unit_tests/utils/test_ignorereason.py b/posydon/unit_tests/utils/test_ignorereason.py index 603f1732b..e93d00b4f 100644 --- a/posydon/unit_tests/utils/test_ignorereason.py +++ b/posydon/unit_tests/utils/test_ignorereason.py @@ -51,12 +51,12 @@ def test_value_IGNORE_REASONS_PRIORITY(self): class TestIgnoreReason: - # test the IgnoreReason class @fixture def NewInstance(self): # initialize an instance of the class for each test return totest.IgnoreReason() + # test the IgnoreReason class def test_init(self, NewInstance): assert isroutine(NewInstance.__init__) # check that the instance is of correct type and all code in the @@ -96,5 +96,5 @@ def test_setattr(self, NewInstance): assert NewInstance.order is None # try error on non existing reason assert '' not in totest.IGNORE_REASONS_PRIORITY - with raises(ValueError): + with raises(ValueError, match="Ignore reason `` not recognized"): NewInstance.reason = '' diff --git a/posydon/unit_tests/utils/test_posydonerror.py b/posydon/unit_tests/utils/test_posydonerror.py index a57d820e1..18e07101a 100644 --- a/posydon/unit_tests/utils/test_posydonerror.py +++ b/posydon/unit_tests/utils/test_posydonerror.py @@ -6,7 +6,6 @@ ] # import the module which will be tested -import unittest import posydon.utils.posydonerror as totest # import other needed code for the tests, which is not already imported in the @@ -101,7 +100,6 @@ def test_initial_condition_message(self, BinaryStar, artificial_object): class TestPOSYDONError: - # test the POSYDONError class @fixture def POSYDONError(self): # initialize an instance of the class with defaults @@ -139,6 +137,7 @@ def POSYDONError_List(self, artificial_object, SingleStar, BinaryStar): return totest.POSYDONError(objects=[artificial_object, SingleStar,\ BinaryStar]) + # test the POSYDONError class def test_init(self, POSYDONError, POSYDONError_position, POSYDONError_key,\ POSYDONError_object, artificial_object): assert isroutine(POSYDONError.__init__) diff --git a/posydon/unit_tests/utils/test_posydonwarning.py b/posydon/unit_tests/utils/test_posydonwarning.py index 76c493a4d..5366d7162 100644 --- a/posydon/unit_tests/utils/test_posydonwarning.py +++ b/posydon/unit_tests/utils/test_posydonwarning.py @@ -5,17 +5,16 @@ "Matthias Kruckow " ] -# import the unittest module and the module which will be tested -import unittest +# import the module which will be tested import posydon.utils.posydonwarning as totest -# import other needed code for the tests +# import other needed code for the tests, which is not already imported in the +# module you like to test +from pytest import fixture, raises, warns, mark from inspect import isclass, isroutine -from io import StringIO -from contextlib import redirect_stdout, redirect_stderr -# define test classes -class TestElements(unittest.TestCase): +# define test classes collecting several test functions +class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): elements = ['AllPOSYDONWarnings', 'ApproximationWarning', @@ -33,444 +32,466 @@ def test_dir(self): '_apply_POSYDON_filter', '_get_POSYDONWarning_class', '_issue_warn', 'copy', 'get_stats', 'print_stats', 'sys', 'warnings'] - self.assertListEqual(dir(totest), elements, - msg="There might be added or removed objects " - "without an update on the unit test.") + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." def test_instance_POSYDONWarning(self): - self.assertTrue(isclass(totest.POSYDONWarning)) - self.assertTrue(issubclass(totest.POSYDONWarning, Warning)) + assert isclass(totest.POSYDONWarning) + assert issubclass(totest.POSYDONWarning, Warning) def test_instance_ApproximationWarning(self): - self.assertTrue(isclass(totest.ApproximationWarning)) - self.assertTrue(issubclass(totest.ApproximationWarning, - totest.POSYDONWarning)) + assert isclass(totest.ApproximationWarning) + assert issubclass(totest.ApproximationWarning, totest.POSYDONWarning) def test_instance_BinaryParsingWarning(self): - self.assertTrue(isclass(totest.BinaryParsingWarning)) - self.assertTrue(issubclass(totest.BinaryParsingWarning, - totest.POSYDONWarning)) + assert isclass(totest.BinaryParsingWarning) + assert issubclass(totest.BinaryParsingWarning, totest.POSYDONWarning) def test_instance_ClassificationWarning(self): - self.assertTrue(isclass(totest.ClassificationWarning)) - self.assertTrue(issubclass(totest.ClassificationWarning, - totest.POSYDONWarning)) + assert isclass(totest.ClassificationWarning) + assert issubclass(totest.ClassificationWarning, totest.POSYDONWarning) def test_instance_EvolutionWarning(self): - self.assertTrue(isclass(totest.EvolutionWarning)) - self.assertTrue(issubclass(totest.EvolutionWarning, - totest.POSYDONWarning)) + assert isclass(totest.EvolutionWarning) + assert issubclass(totest.EvolutionWarning, totest.POSYDONWarning) def test_instance_InappropriateValueWarning(self): - self.assertTrue(isclass(totest.InappropriateValueWarning)) - self.assertTrue(issubclass(totest.InappropriateValueWarning, - totest.POSYDONWarning)) + assert isclass(totest.InappropriateValueWarning) + assert issubclass(totest.InappropriateValueWarning, + totest.POSYDONWarning) def test_instance_IncompletenessWarning(self): - self.assertTrue(isclass(totest.IncompletenessWarning)) - self.assertTrue(issubclass(totest.IncompletenessWarning, - totest.POSYDONWarning)) + assert isclass(totest.IncompletenessWarning) + assert issubclass(totest.IncompletenessWarning, totest.POSYDONWarning) def test_instance_InterpolationWarning(self): - self.assertTrue(isclass(totest.InterpolationWarning)) - self.assertTrue(issubclass(totest.InterpolationWarning, - totest.POSYDONWarning)) + assert isclass(totest.InterpolationWarning) + assert issubclass(totest.InterpolationWarning, totest.POSYDONWarning) def test_instance_MissingFilesWarning(self): - self.assertTrue(isclass(totest.MissingFilesWarning)) - self.assertTrue(issubclass(totest.MissingFilesWarning, - totest.POSYDONWarning)) + assert isclass(totest.MissingFilesWarning) + assert issubclass(totest.MissingFilesWarning, totest.POSYDONWarning) def test_instance_OverwriteWarning(self): - self.assertTrue(isclass(totest.OverwriteWarning)) - self.assertTrue(issubclass(totest.OverwriteWarning, - totest.POSYDONWarning)) + assert isclass(totest.OverwriteWarning) + assert issubclass(totest.OverwriteWarning, totest.POSYDONWarning) def test_instance_ReplaceValueWarning(self): - self.assertTrue(isclass(totest.ReplaceValueWarning)) - self.assertTrue(issubclass(totest.ReplaceValueWarning, - totest.POSYDONWarning)) + assert isclass(totest.ReplaceValueWarning) + assert issubclass(totest.ReplaceValueWarning, totest.POSYDONWarning) def test_instance_UnsupportedModelWarning(self): - self.assertTrue(isclass(totest.UnsupportedModelWarning)) - self.assertTrue(issubclass(totest.UnsupportedModelWarning, - totest.POSYDONWarning)) + assert isclass(totest.UnsupportedModelWarning) + assert issubclass(totest.UnsupportedModelWarning, + totest.POSYDONWarning) def test_instance_POSYDONWarning_subclasses(self): - self.assertIsInstance(totest._POSYDONWarning_subclasses, (dict)) + assert isinstance(totest._POSYDONWarning_subclasses, (dict)) def test_instance_get_POSYDONWarning_class(self): - self.assertTrue(isroutine(totest._get_POSYDONWarning_class)) + assert isroutine(totest._get_POSYDONWarning_class) def test_instance_POSYDON_WARNINGS_REGISTRY(self): - self.assertIsInstance(totest._POSYDON_WARNINGS_REGISTRY, (dict)) + assert isinstance(totest._POSYDON_WARNINGS_REGISTRY, (dict)) def test_instance_get_stats(self): - self.assertTrue(isroutine(totest.get_stats)) + assert isroutine(totest.get_stats) def test_instance_print_stats(self): - self.assertTrue(isroutine(totest.print_stats)) + assert isroutine(totest.print_stats) def test_instance_apply_POSYDON_filter(self): - self.assertTrue(isroutine(totest._apply_POSYDON_filter)) + assert isroutine(totest._apply_POSYDON_filter) def test_instance_issue_warn(self): - self.assertTrue(isroutine(totest._issue_warn)) + assert isroutine(totest._issue_warn) def test_instance_Caught_POSYDON_Warnings(self): - self.assertTrue(isclass(totest._Caught_POSYDON_Warnings)) + assert isclass(totest._Caught_POSYDON_Warnings) def test_instance_CAUGHT_POSYDON_WARNINGS(self): - self.assertIsInstance(totest._CAUGHT_POSYDON_WARNINGS, - totest._Caught_POSYDON_Warnings) + assert isinstance(totest._CAUGHT_POSYDON_WARNINGS, + totest._Caught_POSYDON_Warnings) def test_instance_Catch_POSYDON_Warnings(self): - self.assertTrue(isclass(totest.Catch_POSYDON_Warnings)) + assert isclass(totest.Catch_POSYDON_Warnings) def test_instance_Pwarn(self): - self.assertTrue(isroutine(totest.Pwarn)) + assert isroutine(totest.Pwarn) def test_instance_SetPOSYDONWarnings(self): - self.assertTrue(isroutine(totest.SetPOSYDONWarnings)) + assert isroutine(totest.SetPOSYDONWarnings) def test_instance_NoPOSYDONWarnings(self): - self.assertTrue(isroutine(totest.NoPOSYDONWarnings)) + assert isroutine(totest.NoPOSYDONWarnings) def test_instance_AllPOSYDONWarnings(self): - self.assertTrue(isroutine(totest.AllPOSYDONWarnings)) + assert isroutine(totest.AllPOSYDONWarnings) -class TestValues(unittest.TestCase): +class TestValues: # check that the values fit def test_value_POSYDONWarning_subclasses(self): - self.assertIn('ApproximationWarning', - totest._POSYDONWarning_subclasses) + assert 'ApproximationWarning' in totest._POSYDONWarning_subclasses def test_value_POSYDON_WARNINGS_REGISTRY(self): - self.assertDictEqual({}, totest._POSYDON_WARNINGS_REGISTRY) + assert totest._POSYDON_WARNINGS_REGISTRY == {} def test_value_CAUGHT_POSYDON_WARNINGS(self): - self.assertFalse(totest._CAUGHT_POSYDON_WARNINGS.catch_warnings) - self.assertTrue(totest._CAUGHT_POSYDON_WARNINGS.record) - self.assertTrue(totest._CAUGHT_POSYDON_WARNINGS.filter_first) - self.assertListEqual(totest._CAUGHT_POSYDON_WARNINGS.caught_warnings, - []) - self.assertEqual(totest._CAUGHT_POSYDON_WARNINGS.registry, - totest._POSYDON_WARNINGS_REGISTRY) - - -class TestFunctions(unittest.TestCase): - def tearDown(self): + assert totest._CAUGHT_POSYDON_WARNINGS.catch_warnings == False + assert totest._CAUGHT_POSYDON_WARNINGS.record + assert totest._CAUGHT_POSYDON_WARNINGS.filter_first + assert totest._CAUGHT_POSYDON_WARNINGS.caught_warnings == [] + assert totest._CAUGHT_POSYDON_WARNINGS.registry is\ + totest._POSYDON_WARNINGS_REGISTRY + + +class TestFunctions: + @fixture + def clear_registry(self): + yield # empyt the global POSYDON warnings registry after each test keys = [] for k in totest._POSYDON_WARNINGS_REGISTRY: keys.append(k) for k in keys: del totest._POSYDON_WARNINGS_REGISTRY[k] + + @fixture + def reset_filter(self): + yield # set POSYDON warnings back to default - totest.SetPOSYDONWarnings() + totest.warnings.filterwarnings(action='ignore',\ + category=ResourceWarning) + totest.warnings.filterwarnings(action='default',\ + category=totest.POSYDONWarning) # test functions def test_get_POSYDONWarning_class(self): # missing argument - with self.assertRaises(TypeError): + with raises(TypeError, match="missing 1 required positional argument"): totest._get_POSYDONWarning_class() # default to POSYDONWarning - self.assertEqual(totest._get_POSYDONWarning_class(""), - totest.POSYDONWarning) - self.assertEqual(totest._get_POSYDONWarning_class( - totest.POSYDONWarning), totest.POSYDONWarning) + assert totest._get_POSYDONWarning_class("") == totest.POSYDONWarning + assert totest._get_POSYDONWarning_class(totest.POSYDONWarning) ==\ + totest.POSYDONWarning # check subclasses of POSYDONWarning for k,v in totest._POSYDONWarning_subclasses.items(): - self.assertEqual(totest._get_POSYDONWarning_class(k), v) - self.assertEqual(totest._get_POSYDONWarning_class(v), v) + assert totest._get_POSYDONWarning_class(k) == v + assert totest._get_POSYDONWarning_class(v) == v # bad input - self.assertIsNone(totest._get_POSYDONWarning_class(1)) + assert totest._get_POSYDONWarning_class(1) is None def test_get_stats(self): - self.assertEqual(totest.get_stats(), totest._POSYDON_WARNINGS_REGISTRY) + assert totest.get_stats() == totest._POSYDON_WARNINGS_REGISTRY - def test_print_stats(self): + def test_print_stats(self, capsys, clear_registry): # no warnings to print - with redirect_stdout(StringIO()) as print_out: - totest.print_stats() - self.assertEqual("No POSYDON warnings occured.\n", - print_out.getvalue()) + totest.print_stats() + assert "No POSYDON warnings occured.\n" == capsys.readouterr().out # add an artifical entry in the warnings registry and get the printout totest._POSYDON_WARNINGS_REGISTRY = {'Unit': 'Test'} - with redirect_stdout(StringIO()) as print_out: - totest.print_stats() - self.assertEqual("There have been POSYDON warnings in the global "+ - "registry:\n "+str(totest._POSYDON_WARNINGS_REGISTRY)+ - "\n", print_out.getvalue()) + totest.print_stats() + assert "There have been POSYDON warnings in the global registry:\n "+\ + str(totest._POSYDON_WARNINGS_REGISTRY)+"\n" ==\ + capsys.readouterr().out - def test_apply_POSYDON_filter(self): + def test_apply_POSYDON_filter(self, capsys, clear_registry, reset_filter): # wrong arguments - with self.assertRaises(TypeError): + with raises(TypeError, match="warning must be a dictionary"): totest._apply_POSYDON_filter(warning="Test") - with self.assertRaises(TypeError): + with raises(TypeError, match="message must be a string"): totest._apply_POSYDON_filter(warning={'message': 1}) - with self.assertRaises(TypeError): + with raises(TypeError, match="stacklevel must be an integer"): totest._apply_POSYDON_filter(warning={'stacklevel': "Test"}) - with redirect_stdout(StringIO()) as print_out: + with raises(TypeError, match="registry must be a dictionary or None"): totest._apply_POSYDON_filter(registry="Test") - self.assertEqual("Reset registry, old was: Test\n", - print_out.getvalue()) # check default warning - self.assertDictEqual(dict(message="No warning"), - totest._apply_POSYDON_filter()) + assert totest._apply_POSYDON_filter() == dict(message="No warning") # check that further default warnings are filtered out but added to the # registry for i in range(10): for k,v in totest._POSYDON_WARNINGS_REGISTRY.items(): - self.assertEqual(i+1, v) - self.assertIsNone(totest._apply_POSYDON_filter()) + assert i+1 == v + assert totest._apply_POSYDON_filter() is None + # check usage of python filter with a python warning + totest.warnings.filterwarnings(action='ignore',\ + category=ResourceWarning) + assert totest._apply_POSYDON_filter(warning={'message': "Test",\ + 'category': ResourceWarning}) is None + # check the route of an always filter + totest.warnings.filterwarnings(action='always',\ + category=ResourceWarning) + for i in range(10): + assert totest._apply_POSYDON_filter(warning={'message': "Test"+\ + str(i), 'category': ResourceWarning}) == {'message': \ + "Test"+str(i), 'category': ResourceWarning} + for k,v in totest._POSYDON_WARNINGS_REGISTRY.items(): + if "ResourceWarning" in k: + assert i == v + # check the route of an error filter + totest.warnings.filterwarnings(action='error',\ + category=ResourceWarning) + assert totest._apply_POSYDON_filter(warning={'message': "Test"+str(i),\ + 'category': ResourceWarning}) == {'message': "Test"+str(i),\ + 'category': ResourceWarning} + + def test_issue_warn(self, capsys, monkeypatch, recwarn, clear_registry): + def mock_apply_POSYDON_filter(warning=dict(message="No warning"), registry=None): + return None - def test_issue_warn(self): # wrong arguments - with self.assertRaises(TypeError): + with raises(TypeError, match="warning must be a dictionary"): totest._issue_warn(warning="Test") - with self.assertRaises(TypeError): + with raises(TypeError, match="message must be a string"): totest._issue_warn(warning={'message': 1}) - with self.assertRaises(TypeError): + with raises(TypeError, match="stacklevel must be an integer"): totest._issue_warn(warning={'stacklevel': "Test"}) - with redirect_stdout(StringIO()) as print_out: - # it will issue a default warning on the reset registry - with redirect_stderr(StringIO()) as print_err: - totest._issue_warn(registry="Test") - self.assertEqual("Reset registry, old was: Test\n", - print_out.getvalue()) + with raises(TypeError, match="registry must be a dictionary or None"): + totest._issue_warn(registry="Test") # check default warning - self.assertIn("UserWarning: No warning", print_err.getvalue()) + with warns(UserWarning, match="No warning") as warn1: + totest._issue_warn() + assert len(warn1) == 1 # check filtered warning - self.assertIsNone(totest._issue_warn()) + monkeypatch.setattr(totest, "_apply_POSYDON_filter", mock_apply_POSYDON_filter) + totest._issue_warn() + assert len(recwarn) == 0 def test_Pwarn(self): - # wrong arguments - with self.assertRaises(TypeError): + # missing argument + with raises(TypeError, match="missing 1 required positional argument"): totest.Pwarn() - with self.assertRaises(TypeError): + # wrong arguments + with raises(TypeError, match="message must be a string"): totest.Pwarn(1) - with self.assertRaises(TypeError): + with raises(TypeError, match="stacklevel must be an integer"): totest.Pwarn("Unit", stacklevel="Test") - # check output of POSYDONwarning and UserWarning - with redirect_stderr(StringIO()) as print_err: + # check output of POSYDONwarning + with warns(totest.POSYDONWarning, match="Unit test") as warn1: totest.Pwarn("Unit test", "POSYDONWarning") - self.assertIn("POSYDONWarning: 'Unit test'", print_err.getvalue()) - with redirect_stderr(StringIO()) as print_err: + assert len(warn1) == 1 + # check output of FutureWarning + with warns(FutureWarning, match="Unit test") as warn2: + totest.Pwarn("Unit test", FutureWarning) + assert len(warn2) == 1 + # check output of UserWarning (default if unspecified) + with warns(UserWarning, match="Unit test") as warn3: totest.Pwarn("Unit test") - self.assertIn("UserWarning: Unit test", print_err.getvalue()) + assert len(warn3) == 1 def test_SetPOSYDONWarnings(self): + totest.SetPOSYDONWarnings(action="once") + assert str(totest.warnings.filters[0]) == "('once', None, , None, 0)" totest.SetPOSYDONWarnings() - self.assertEqual("('default', None, , " - "None, 0)", str(totest.warnings.filters[0])) - - def test_NoPOSYDONWarnings(self): + assert str(totest.warnings.filters[0]) == "('default', None, , None, 0)" + # non POSYDON warnings have no effect + totest.SetPOSYDONWarnings(category=UserWarning) + assert str(totest.warnings.filters[0]) == "('default', None, , None, 0)" + + def test_NoPOSYDONWarnings(self, reset_filter): totest.NoPOSYDONWarnings() - self.assertEqual("('ignore', None, , " - "None, 0)", str(totest.warnings.filters[0])) + assert str(totest.warnings.filters[0]) == "('ignore', None, , None, 0)" - def test_AllPOSYDONWarnings(self): + def test_AllPOSYDONWarnings(self, reset_filter): totest.AllPOSYDONWarnings() - self.assertEqual("('always', None, , " - "None, 0)", str(totest.warnings.filters[0])) + assert str(totest.warnings.filters[0]) == "('always', None, , None, 0)" -class TestPOSYDONWarning(unittest.TestCase): - # test the POSYDONWarning class - def setUp(self): - # initialize an instance of the class for each test - self.POSYDONWarning = totest.POSYDONWarning() +class TestPOSYDONWarning: + @fixture + def POSYDONWarning(self): + # initialize an instance of the class with defaults + return totest.POSYDONWarning() - def test_init(self): - self.assertTrue(isroutine(self.POSYDONWarning.__init__)) + # test the POSYDONWarning class + def test_init(self, POSYDONWarning): + assert isroutine(POSYDONWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.POSYDONWarning, totest.POSYDONWarning) - self.assertEqual('', self.POSYDONWarning.message) + assert isinstance(POSYDONWarning, totest.POSYDONWarning) + assert POSYDONWarning.message == '' - def test_str(self): - self.assertTrue(isroutine(self.POSYDONWarning.__str__)) - self.assertEqual("''", str(self.POSYDONWarning)) + def test_str(self, POSYDONWarning): + assert isroutine(POSYDONWarning.__str__) + assert str(POSYDONWarning) == "''" -class TestApproximationWarning(unittest.TestCase): - # test the ApproximationWarning class - def setUp(self): - # initialize an instance of the class for each test - self.ApproximationWarning = totest.ApproximationWarning() +class TestApproximationWarning: + @fixture + def ApproximationWarning(self): + # initialize an instance of the class with defaults + return totest.ApproximationWarning() - def test_init(self): - self.assertTrue(isroutine(self.ApproximationWarning.__init__)) + # test the ApproximationWarning class + def test_init(self, ApproximationWarning): + assert isroutine(ApproximationWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.ApproximationWarning, - totest.ApproximationWarning) - self.assertEqual('', self.ApproximationWarning.message) + assert isinstance(ApproximationWarning, totest.ApproximationWarning) + assert ApproximationWarning.message == '' -class TestBinaryParsingWarning(unittest.TestCase): - # test the BinaryParsingWarning class - def setUp(self): - # initialize an instance of the class for each test - self.BinaryParsingWarning = totest.BinaryParsingWarning() +class TestBinaryParsingWarning: + @fixture + def BinaryParsingWarning(self): + # initialize an instance of the class with defaults + return totest.BinaryParsingWarning() - def test_init(self): - self.assertTrue(isroutine(self.BinaryParsingWarning.__init__)) + # test the BinaryParsingWarning class + def test_init(self, BinaryParsingWarning): + assert isroutine(BinaryParsingWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.BinaryParsingWarning, - totest.BinaryParsingWarning) - self.assertEqual('', self.BinaryParsingWarning.message) + assert isinstance(BinaryParsingWarning, totest.BinaryParsingWarning) + assert BinaryParsingWarning.message == '' -class TestClassificationWarning(unittest.TestCase): - # test the ClassificationWarning class - def setUp(self): - # initialize an instance of the class for each test - self.ClassificationWarning = totest.ClassificationWarning() +class TestClassificationWarning: + @fixture + def ClassificationWarning(self): + # initialize an instance of the class with defaults + return totest.ClassificationWarning() - def test_init(self): - self.assertTrue(isroutine(self.ClassificationWarning.__init__)) + # test the ClassificationWarning class + def test_init(self, ClassificationWarning): + assert isroutine(ClassificationWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.ClassificationWarning, - totest.ClassificationWarning) - self.assertEqual('', self.ClassificationWarning.message) + assert isinstance(ClassificationWarning, totest.ClassificationWarning) + assert ClassificationWarning.message == '' -class TestEvolutionWarning(unittest.TestCase): - # test the EvolutionWarning class - def setUp(self): - # initialize an instance of the class for each test - self.EvolutionWarning = totest.EvolutionWarning() +class TestEvolutionWarning: + @fixture + def EvolutionWarning(self): + # initialize an instance of the class with defaults + return totest.EvolutionWarning() - def test_init(self): - self.assertTrue(isroutine(self.EvolutionWarning.__init__)) + # test the EvolutionWarning class + def test_init(self, EvolutionWarning): + assert isroutine(EvolutionWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.EvolutionWarning, totest.EvolutionWarning) - self.assertEqual('', self.EvolutionWarning.message) + assert isinstance(EvolutionWarning, totest.EvolutionWarning) + assert EvolutionWarning.message == '' -class TestInappropriateValueWarning(unittest.TestCase): - # test the InappropriateValueWarning class - def setUp(self): - # initialize an instance of the class for each test - self.InappropriateValueWarning = totest.InappropriateValueWarning() +class TestInappropriateValueWarning: + @fixture + def InappropriateValueWarning(self): + # initialize an instance of the class with defaults + return totest.InappropriateValueWarning() - def test_init(self): - self.assertTrue(isroutine(self.InappropriateValueWarning.__init__)) + # test the InappropriateValueWarning class + def test_init(self, InappropriateValueWarning): + assert isroutine(InappropriateValueWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.InappropriateValueWarning, - totest.InappropriateValueWarning) - self.assertEqual('', self.InappropriateValueWarning.message) + assert isinstance(InappropriateValueWarning, totest.InappropriateValueWarning) + assert InappropriateValueWarning.message == '' -class TestIncompletenessWarning(unittest.TestCase): - # test the IncompletenessWarning class - def setUp(self): - # initialize an instance of the class for each test - self.IncompletenessWarning = totest.IncompletenessWarning() +class TestIncompletenessWarning: + @fixture + def IncompletenessWarning(self): + # initialize an instance of the class with defaults + return totest.IncompletenessWarning() - def test_init(self): - self.assertTrue(isroutine(self.IncompletenessWarning.__init__)) + # test the IncompletenessWarning class + def test_init(self, IncompletenessWarning): + assert isroutine(IncompletenessWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.IncompletenessWarning, - totest.IncompletenessWarning) - self.assertEqual('', self.IncompletenessWarning.message) + assert isinstance(IncompletenessWarning, totest.IncompletenessWarning) + assert IncompletenessWarning.message == '' -class TestInterpolationWarning(unittest.TestCase): - # test the InterpolationWarning class - def setUp(self): - # initialize an instance of the class for each test - self.InterpolationWarning = totest.InterpolationWarning() +class TestInterpolationWarning: + @fixture + def InterpolationWarning(self): + # initialize an instance of the class with defaults + return totest.InterpolationWarning() - def test_init(self): - self.assertTrue(isroutine(self.InterpolationWarning.__init__)) + # test the InterpolationWarning class + def test_init(self, InterpolationWarning): + assert isroutine(InterpolationWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.InterpolationWarning, - totest.InterpolationWarning) - self.assertEqual('', self.InterpolationWarning.message) + assert isinstance(InterpolationWarning, totest.InterpolationWarning) + assert InterpolationWarning.message == '' -class TestMissingFilesWarning(unittest.TestCase): - # test the MissingFilesWarning class - def setUp(self): - # initialize an instance of the class for each test - self.MissingFilesWarning = totest.MissingFilesWarning() +class TestMissingFilesWarning: + @fixture + def MissingFilesWarning(self): + # initialize an instance of the class with defaults + return totest.MissingFilesWarning() - def test_init(self): - self.assertTrue(isroutine(self.MissingFilesWarning.__init__)) + # test the MissingFilesWarning class + def test_init(self, MissingFilesWarning): + assert isroutine(MissingFilesWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.MissingFilesWarning, - totest.MissingFilesWarning) - self.assertEqual('', self.MissingFilesWarning.message) + assert isinstance(MissingFilesWarning, totest.MissingFilesWarning) + assert MissingFilesWarning.message == '' -class TestOverwriteWarning(unittest.TestCase): - # test the OverwriteWarning class - def setUp(self): - # initialize an instance of the class for each test - self.OverwriteWarning = totest.OverwriteWarning() +class TestOverwriteWarning: + @fixture + def OverwriteWarning(self): + # initialize an instance of the class with defaults + return totest.OverwriteWarning() - def test_init(self): - self.assertTrue(isroutine(self.OverwriteWarning.__init__)) + # test the OverwriteWarning class + def test_init(self, OverwriteWarning): + assert isroutine(OverwriteWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.OverwriteWarning, - totest.OverwriteWarning) - self.assertEqual('', self.OverwriteWarning.message) + assert isinstance(OverwriteWarning, totest.OverwriteWarning) + assert OverwriteWarning.message == '' -class TestReplaceValueWarning(unittest.TestCase): - # test the ReplaceValueWarning class - def setUp(self): - # initialize an instance of the class for each test - self.ReplaceValueWarning = totest.ReplaceValueWarning() +class TestReplaceValueWarning: + @fixture + def ReplaceValueWarning(self): + # initialize an instance of the class with defaults + return totest.ReplaceValueWarning() - def test_init(self): - self.assertTrue(isroutine(self.ReplaceValueWarning.__init__)) + # test the ReplaceValueWarning class + def test_init(self, ReplaceValueWarning): + assert isroutine(ReplaceValueWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.ReplaceValueWarning, - totest.ReplaceValueWarning) - self.assertEqual('', self.ReplaceValueWarning.message) + assert isinstance(ReplaceValueWarning, totest.ReplaceValueWarning) + assert ReplaceValueWarning.message == '' -class TestUnsupportedModelWarning(unittest.TestCase): - # test the UnsupportedModelWarning class - def setUp(self): - # initialize an instance of the class for each test - self.UnsupportedModelWarning = totest.UnsupportedModelWarning() +class TestUnsupportedModelWarning: + @fixture + def UnsupportedModelWarning(self): + # initialize an instance of the class with defaults + return totest.UnsupportedModelWarning() - def test_init(self): - self.assertTrue(isroutine(self.UnsupportedModelWarning.__init__)) + # test the UnsupportedModelWarning class + def test_init(self, UnsupportedModelWarning): + assert isroutine(UnsupportedModelWarning.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.UnsupportedModelWarning, - totest.UnsupportedModelWarning) - self.assertEqual('', self.UnsupportedModelWarning.message) - + assert isinstance(UnsupportedModelWarning,\ + totest.UnsupportedModelWarning) + assert UnsupportedModelWarning.message == '' -class Test_Caught_POSYDON_Warnings(unittest.TestCase): - # test the _Caught_POSYDON_Warnings class - def setUp(self): - # initialize an instance of the class for each test - self._Caught_POSYDON_Warnings = totest._Caught_POSYDON_Warnings() - def tearDown(self): - # empty the cache to not print remaining records - self._Caught_POSYDON_Warnings(empty_cache=True) +class Test_Caught_POSYDON_Warnings: + @fixture + def clear_registry(self): + yield # empyt the global POSYDON warnings registry after each test keys = [] for k in totest._POSYDON_WARNINGS_REGISTRY: @@ -478,115 +499,193 @@ def tearDown(self): for k in keys: del totest._POSYDON_WARNINGS_REGISTRY[k] - def test_init(self): - self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.__init__)) + @fixture + def _Caught_POSYDON_Warnings(self, clear_registry): + # initialize an instance of the class with defaults + _Caught_POSYDON_Warnings = totest._Caught_POSYDON_Warnings() + yield _Caught_POSYDON_Warnings + # empty the cache to not print remaining records + _Caught_POSYDON_Warnings.caught_warnings = [] + + @fixture + def test_dict(self): + # a dictionary as a registry for testing + return {'Unit': "Test"} + + # test the _Caught_POSYDON_Warnings class + def test_init(self, _Caught_POSYDON_Warnings): + assert isroutine(_Caught_POSYDON_Warnings.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self._Caught_POSYDON_Warnings, - totest._Caught_POSYDON_Warnings) - self.assertFalse(self._Caught_POSYDON_Warnings.catch_warnings) - self.assertListEqual([], self._Caught_POSYDON_Warnings.caught_warnings) - self.assertTrue(self._Caught_POSYDON_Warnings.record) - self.assertTrue(self._Caught_POSYDON_Warnings.filter_first) - self.assertFalse(self._Caught_POSYDON_Warnings._got_called) - self.assertDictEqual(totest._POSYDON_WARNINGS_REGISTRY, - self._Caught_POSYDON_Warnings.registry) + assert isinstance(_Caught_POSYDON_Warnings,\ + totest._Caught_POSYDON_Warnings) + assert _Caught_POSYDON_Warnings.catch_warnings == False + assert _Caught_POSYDON_Warnings.caught_warnings == [] + assert _Caught_POSYDON_Warnings.record + assert _Caught_POSYDON_Warnings.filter_first + assert _Caught_POSYDON_Warnings._got_called == False + assert _Caught_POSYDON_Warnings.registry is\ + totest._POSYDON_WARNINGS_REGISTRY # bad input - with self.assertRaises(TypeError): + with raises(TypeError, match="catch_warnings must be a boolean"): totest._Caught_POSYDON_Warnings(catch_warnings="Test") - with self.assertRaises(TypeError): + with raises(TypeError, match="record must be a boolean"): totest._Caught_POSYDON_Warnings(record="Test") - with self.assertRaises(TypeError): + with raises(TypeError, match="filter_first must be a boolean"): totest._Caught_POSYDON_Warnings(filter_first="Test") - with self.assertRaises(TypeError): + with raises(TypeError, match="registry must be a dictionary"): totest._Caught_POSYDON_Warnings(registry="Test") - def test_str(self): - self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.__str__)) - self.assertEqual("POSYDON warnings are shown.", - str(self._Caught_POSYDON_Warnings)) - # check with catching - caught_object = totest._Caught_POSYDON_Warnings(catch_warnings=True) - self.assertEqual("POSYDON warnings will be caught and recorded. " - "Filters are applied before recording.", - str(caught_object)) - # check without recording and own registry - test_registry = {'Unit': "Test"} - caught_object = totest._Caught_POSYDON_Warnings(catch_warnings=True, - record=False, - registry=test_registry) - self.assertEqual("POSYDON warnings will be caught and discarded. " - "Currently a private registry is used, it contains:\n" - "{'Unit': 'Test'}", str(caught_object)) - - def test_call(self): - self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.__call__)) + def test_str(self, _Caught_POSYDON_Warnings, test_dict): + assert isroutine(_Caught_POSYDON_Warnings.__str__) + assert str(_Caught_POSYDON_Warnings) == "POSYDON warnings are shown." + # check with different setups + test_cases = [{'catch_warnings': True, 'record': True,\ + 'filter_first': True, 'registry': None,\ + 'str': "POSYDON warnings will be caught and recorded."+\ + " Filters are applied before recording."\ + },{'catch_warnings': True, 'record': True,\ + 'filter_first': False, 'registry': None,\ + 'str': "POSYDON warnings will be caught and recorded."\ + },{'catch_warnings': True, 'record': False,\ + 'filter_first': True, 'registry': None,\ + 'str': "POSYDON warnings will be caught and discarded."\ + },{'catch_warnings': True, 'record': False,\ + 'filter_first': False, 'registry': None,\ + 'str': "POSYDON warnings will be caught and discarded."\ + },{'catch_warnings': False, 'record': True,\ + 'filter_first': True, 'registry': test_dict,\ + 'str': "Currently a private registry is used, it "+\ + "contains:\n{'Unit': 'Test'}"\ + }] + for tc in test_cases: + caught_object = totest._Caught_POSYDON_Warnings(catch_warnings=\ + tc['catch_warnings'], record=tc['record'],\ + filter_first=tc['filter_first'],\ + registry=tc['registry']) + assert tc['str'] in str(caught_object) + # check artifical caught_warnings + for i in range(4): + _Caught_POSYDON_Warnings.caught_warnings = i*[test_dict] + if i==1: + assert "There is 1 warning recorded." in\ + str(_Caught_POSYDON_Warnings) + elif i>1: + assert "There are "+str(i)+" warnings recorded.".format(i) in\ + str(_Caught_POSYDON_Warnings) + else: + assert "recorded" not in str(_Caught_POSYDON_Warnings) + + def test_call(self, _Caught_POSYDON_Warnings, test_dict): + assert isroutine(_Caught_POSYDON_Warnings.__call__) + assert _Caught_POSYDON_Warnings._got_called == False # bad input - with self.assertRaises(ValueError): - self._Caught_POSYDON_Warnings() - self.assertTrue(self._Caught_POSYDON_Warnings._got_called) - with self.assertRaises(TypeError): - self._Caught_POSYDON_Warnings(change_settings="Test") - with self.assertRaises(TypeError): - self._Caught_POSYDON_Warnings(change_settings={'catch_warnings': - "Test"}) - with self.assertRaises(AttributeError): - self._Caught_POSYDON_Warnings(change_settings={'Unit': "Test"}) + with raises(ValueError, match="Nothing to do: either empty_cache has"+\ + " to be True or new_warning/"+\ + "change_settings needs to be set."): + _Caught_POSYDON_Warnings() + assert _Caught_POSYDON_Warnings._got_called + _Caught_POSYDON_Warnings.caught_warnings = [test_dict] + _Caught_POSYDON_Warnings(empty_cache=True) + assert _Caught_POSYDON_Warnings.caught_warnings == [] + # change_settings + with raises(TypeError, match="change_settings has to be a dict"): + _Caught_POSYDON_Warnings(change_settings="Test") + for s in _Caught_POSYDON_Warnings.__dict__: + if s == 'caught_warnings': + _Caught_POSYDON_Warnings(change_settings={s: "Test"}) + elif s == 'registry': + _Caught_POSYDON_Warnings(change_settings={s: "Test"}) + _Caught_POSYDON_Warnings(change_settings={s: None}) + else: + with raises(TypeError, match="has to be a"): + _Caught_POSYDON_Warnings(change_settings={s: "Test"}) + with raises(AttributeError, match=\ + "unknown to _Caught_POSYDON_Warnings"): + _Caught_POSYDON_Warnings(change_settings=test_dict) # change setting to catch warnings and add a new one to the record list - self._Caught_POSYDON_Warnings(change_settings={'catch_warnings': True}, - new_warning={'message': "Test"}) - self.assertTrue(self._Caught_POSYDON_Warnings.catch_warnings) - self.assertListEqual([{'message': "Test"}], - self._Caught_POSYDON_Warnings.caught_warnings) - - def test_del(self): - self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.__del__)) + _Caught_POSYDON_Warnings(change_settings={'catch_warnings': True},\ + new_warning={'message': "Test"}) + assert _Caught_POSYDON_Warnings.catch_warnings + assert _Caught_POSYDON_Warnings.caught_warnings ==\ + [{'message': "Test"}] + # unset catching: recorded warnings are issued and list is emptied + with warns(UserWarning, match="Test"): + _Caught_POSYDON_Warnings(change_settings={'catch_warnings': False}) + assert _Caught_POSYDON_Warnings.caught_warnings == [] + # change setting to catch warnings and add a new one to the record list + _Caught_POSYDON_Warnings(change_settings={'catch_warnings': True,\ + 'filter_first': False},\ + new_warning={'message': "Test",\ + 'stacklevel': -1}) + assert _Caught_POSYDON_Warnings.catch_warnings + assert _Caught_POSYDON_Warnings.filter_first == False + assert _Caught_POSYDON_Warnings.caught_warnings ==\ + [{'message': "Test", 'stacklevel': -1}] + # change setting to record warnings and try to add a new one + _Caught_POSYDON_Warnings(change_settings={'record': False},\ + new_warning={'message': "no record"}) + assert _Caught_POSYDON_Warnings.record == False + assert _Caught_POSYDON_Warnings.caught_warnings ==\ + [{'message': "Test", 'stacklevel': -1}] # still has old record + # unset catching: recorded warnings are issued and list is emptied + with warns(UserWarning, match="Test") as winfo: + _Caught_POSYDON_Warnings(change_settings={'catch_warnings': False}) + assert "posydonwarning.py" in winfo._list[0].filename + assert _Caught_POSYDON_Warnings.caught_warnings == [] + + def test_del(self, capsys, _Caught_POSYDON_Warnings): + assert isroutine(_Caught_POSYDON_Warnings.__del__) # create an object with a catched warning, call the destructor and # empty it while recording the stdout and stderr - with redirect_stderr(StringIO()) as print_err: - caught_object = totest._Caught_POSYDON_Warnings( - catch_warnings=True) - caught_object(new_warning={'message': "Unit Test"}) + caught_object = totest._Caught_POSYDON_Warnings(catch_warnings=True) + caught_object(new_warning={'message': "Unit Test"}) + with warns(UserWarning, match="Unit Test"): caught_object.__del__() - caught_object(empty_cache=True) - self.assertIn("There are still recorded warnings:", - print_err.getvalue()) - self.assertIn("UserWarning: Unit Test", print_err.getvalue()) - - def test_got_called(self): - self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.got_called)) - self.assertFalse(self._Caught_POSYDON_Warnings.got_called()) - self._Caught_POSYDON_Warnings(empty_cache=True) - self.assertTrue(self._Caught_POSYDON_Warnings.got_called()) - - def test_has_records(self): - self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.has_records)) - self.assertFalse(self._Caught_POSYDON_Warnings.has_records()) - self._Caught_POSYDON_Warnings(change_settings={'catch_warnings': True}, - new_warning={'message': "Unit Test"}) - self.assertTrue(self._Caught_POSYDON_Warnings.has_records()) - - def test_get_cache(self): - self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.get_cache)) - self.assertListEqual([], self._Caught_POSYDON_Warnings.get_cache()) - - def test_reset_cache(self): - self.assertTrue(isroutine(self._Caught_POSYDON_Warnings.reset_cache)) - self._Caught_POSYDON_Warnings(change_settings={'catch_warnings': True}, - new_warning={'message': "Unit Test"}) - self.assertListEqual([{'message': "Unit Test"}], - self._Caught_POSYDON_Warnings.caught_warnings) - self._Caught_POSYDON_Warnings.reset_cache() - self.assertListEqual([], self._Caught_POSYDON_Warnings.caught_warnings) - - -class TestCatch_POSYDON_Warnings(unittest.TestCase): - # test the Catch_POSYDON_Warnings class - def setUp(self): - # initialize an instance of the class for each test - self.Catch_POSYDON_Warnings = totest.Catch_POSYDON_Warnings() - - def tearDown(self): + assert capsys.readouterr().err ==\ + "There are still recorded warnings:\n" + assert caught_object.catch_warnings == False + caught_object.caught_warnings[0]['message'] += "Test" + caught_object.filter_first = False + with warns(UserWarning, match="Unit TestTest"): + caught_object.__del__() + assert capsys.readouterr().err ==\ + "There are still recorded warnings:\n" + caught_object.caught_warnings = [] + + def test_got_called(self, _Caught_POSYDON_Warnings): + assert isroutine(_Caught_POSYDON_Warnings.got_called) + assert _Caught_POSYDON_Warnings.got_called() == False + _Caught_POSYDON_Warnings._got_called = True + assert _Caught_POSYDON_Warnings.got_called() + + def test_has_records(self, _Caught_POSYDON_Warnings, test_dict): + assert isroutine(_Caught_POSYDON_Warnings.has_records) + assert _Caught_POSYDON_Warnings.has_records() == False + _Caught_POSYDON_Warnings.caught_warnings = [test_dict] + assert _Caught_POSYDON_Warnings.has_records() + + def test_get_cache(self, _Caught_POSYDON_Warnings, test_dict): + assert isroutine(_Caught_POSYDON_Warnings.get_cache) + assert _Caught_POSYDON_Warnings.get_cache() == [] + _Caught_POSYDON_Warnings.caught_warnings = [test_dict] + assert _Caught_POSYDON_Warnings.get_cache() == [test_dict] + # clear the cache + assert _Caught_POSYDON_Warnings.get_cache(empty_cache=True) ==\ + [test_dict] + assert _Caught_POSYDON_Warnings.get_cache() == [] + + def test_reset_cache(self, _Caught_POSYDON_Warnings, test_dict): + assert isroutine(_Caught_POSYDON_Warnings.reset_cache) + _Caught_POSYDON_Warnings.caught_warnings = [test_dict] + _Caught_POSYDON_Warnings.reset_cache() + assert _Caught_POSYDON_Warnings.caught_warnings == [] + + +class TestCatch_POSYDON_Warnings: + @fixture + def clear_registry(self): + yield # empyt the global POSYDON warnings registry after each test keys = [] for k in totest._POSYDON_WARNINGS_REGISTRY: @@ -594,32 +693,44 @@ def tearDown(self): for k in keys: del totest._POSYDON_WARNINGS_REGISTRY[k] - def test_init(self): - self.assertTrue(isroutine(self.Catch_POSYDON_Warnings.__init__)) + @fixture + def Catch_POSYDON_Warnings(self, clear_registry): + # initialize an instance of the class with defaults + return totest.Catch_POSYDON_Warnings() + + # test the Catch_POSYDON_Warnings class + def test_init(self, Catch_POSYDON_Warnings): + assert isroutine(Catch_POSYDON_Warnings.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertTrue(self.Catch_POSYDON_Warnings.catch_warnings) - self.assertTrue(self.Catch_POSYDON_Warnings.record) - self.assertTrue(self.Catch_POSYDON_Warnings.filter_first) - self.assertIsNone(self.Catch_POSYDON_Warnings.context_registry) - self.assertIsNone(self.Catch_POSYDON_Warnings.python_catch) + assert Catch_POSYDON_Warnings.catch_warnings + assert Catch_POSYDON_Warnings.record + assert Catch_POSYDON_Warnings.filter_first + assert Catch_POSYDON_Warnings.context_registry is None + assert Catch_POSYDON_Warnings.python_catch is None # check other inputs - catch_object = totest.Catch_POSYDON_Warnings(own_registry=True, + catch_object = totest.Catch_POSYDON_Warnings(own_registry=True,\ use_python_catch=True) - self.assertDictEqual({}, catch_object.context_registry) - self.assertIsInstance(catch_object.python_catch, - totest.warnings.catch_warnings) + assert catch_object.context_registry == {} + assert isinstance(catch_object.python_catch,\ + totest.warnings.catch_warnings) - def test_enter_exit(self): - self.assertTrue(isroutine(self.Catch_POSYDON_Warnings.__enter__)) + def test_enter_exit(self, Catch_POSYDON_Warnings): + assert isroutine(Catch_POSYDON_Warnings.__enter__) - def test_exit(self): - self.assertTrue(isroutine(self.Catch_POSYDON_Warnings.__exit__)) + def test_exit(self, Catch_POSYDON_Warnings): + assert isroutine(Catch_POSYDON_Warnings.__exit__) - def test_context(self): + def test_context(self, capsys): with totest.Catch_POSYDON_Warnings() as cpw: - self.assertEqual(totest._CAUGHT_POSYDON_WARNINGS, cpw) - - -if __name__ == "__main__": - unittest.main() + assert cpw is totest._CAUGHT_POSYDON_WARNINGS + assert cpw.catch_warnings + assert cpw.record + assert cpw.filter_first + assert cpw.registry is totest._POSYDON_WARNINGS_REGISTRY + with totest.Catch_POSYDON_Warnings(use_python_catch=True) as cpw: + assert cpw is totest._CAUGHT_POSYDON_WARNINGS + assert cpw.catch_warnings + assert cpw.record + assert cpw.filter_first + assert cpw.registry is totest._POSYDON_WARNINGS_REGISTRY diff --git a/posydon/utils/posydonwarning.py b/posydon/utils/posydonwarning.py index a645efba0..738b4c86a 100644 --- a/posydon/utils/posydonwarning.py +++ b/posydon/utils/posydonwarning.py @@ -176,9 +176,16 @@ def _apply_POSYDON_filter(warning=dict(message="No warning"), registry=None): ------- warning or None in case it got filtered out. """ + # Check registry and warning if registry is None: global _POSYDON_WARNINGS_REGISTRY + if not isinstance(_POSYDON_WARNINGS_REGISTRY, dict): # pragma: no cover + raise TypeError("_POSYDON_WARNINGS_REGISTRY is corrupt and can't " + "be used for the registry, hence registry can't be" + " None.") registry = _POSYDON_WARNINGS_REGISTRY + elif not isinstance(registry, dict): + raise TypeError("registry must be a dictionary or None.") if not isinstance(warning, dict): raise TypeError("warning must be a dictionary.") # Get stack level @@ -195,7 +202,7 @@ def _apply_POSYDON_filter(warning=dict(message="No warning"), registry=None): # Get filename and lineno from frame at stacklevel try: frame = sys._getframe(stacklevel) - except: + except: # pragma: no cover g = sys.__dict__ filename = "sys" lineno = 1 @@ -206,12 +213,8 @@ def _apply_POSYDON_filter(warning=dict(message="No warning"), registry=None): # Get module if isinstance(g, dict): module = g.get('__name__', "posydonwarnings") - else: + else: # pragma: no cover module = "posydonwarnings" - # Check registry - if not isinstance(registry, dict): - print("Reset registry, old was:", registry) - registry = {} # Set key for registry: # We do not use the warnings text, to allow it to contain detailed # information, while still identifying warnings with same origin @@ -221,7 +224,7 @@ def _apply_POSYDON_filter(warning=dict(message="No warning"), registry=None): if not isinstance(text, str): raise TypeError("message must be a string.") # Search the filters: - # Here we still uses the python filters + # Here we still use the python filters for item in warnings.filters: action, msg, cat, mod, ln = item if ((msg is None or msg.match(text)) and @@ -374,7 +377,10 @@ def __call__(self, new_warning=None, empty_cache=False, (len(self.caught_warnings)>0)): # If there are recorded warnings issue them and empty the list for w in self.caught_warnings: - w["stacklevel"] += 2 + if "stacklevel" in w: + w["stacklevel"] += 2 + else: + w["stacklevel"] = 2 if self.filter_first: warnings.warn(**w) else: From 0283d5c33ea60815b630cb55eb2de612647f1d5b Mon Sep 17 00:00:00 2001 From: mkruckow Date: Mon, 14 Oct 2024 16:33:07 +0200 Subject: [PATCH 14/34] transfer test_data_download.py to pytest --- .../unit_tests/utils/test_data_download.py | 282 +++++++++++------- .../unit_tests/utils/test_posydonwarning.py | 4 +- 2 files changed, 181 insertions(+), 105 deletions(-) diff --git a/posydon/unit_tests/utils/test_data_download.py b/posydon/unit_tests/utils/test_data_download.py index af48bb14d..200b40360 100644 --- a/posydon/unit_tests/utils/test_data_download.py +++ b/posydon/unit_tests/utils/test_data_download.py @@ -5,21 +5,19 @@ "Matthias Kruckow " ] -# import the unittest module and the module which will be tested -import unittest +# import the module which will be tested import posydon.utils.data_download as totest -# import other needed code for the tests -from contextlib import redirect_stdout +# import other needed code for the tests, which is not already imported in the +# module you like to test +from pytest import fixture, raises, approx from inspect import isclass, isroutine -from io import StringIO from shutil import rmtree -import sys +from contextlib import chdir +from unittest.mock import patch -DO_DOWNLOAD = False - -# define test classes -class TestElements(unittest.TestCase): +# define test classes collecting several test functions +class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): elements = ['PATH_TO_POSYDON_DATA', 'ProgressBar', '__authors__', @@ -28,132 +26,210 @@ def test_dir(self): 'data_download', 'data_url', 'file', 'hashlib', 'original_md5', 'os', 'progressbar', 'tarfile', 'tqdm', 'urllib'] - self.assertListEqual(dir(totest), elements, - msg="There might be added or removed objects " - "without an update on the unit test.") + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." def test_instance_PATH_TO_POSYDON_DATA(self): - self.assertIsInstance(totest.PATH_TO_POSYDON_DATA, (str, bytes, - totest.os.PathLike)) + assert isinstance(totest.PATH_TO_POSYDON_DATA, (str, bytes,\ + totest.os.PathLike)) def test_instance_file(self): - self.assertIsInstance(totest.file, (str, bytes, totest.os.PathLike)) + assert isinstance(totest.file, (str, bytes, totest.os.PathLike)) def test_instance_data_url(self): - self.assertIsInstance(totest.data_url, (str, bytes, - totest.os.PathLike)) + assert isinstance(totest.data_url, (str, bytes, totest.os.PathLike)) def test_instance_original_md5(self): - self.assertIsInstance(totest.original_md5, (str, bytes, - totest.os.PathLike)) + assert isinstance(totest.original_md5, (str, bytes,\ + totest.os.PathLike)) def test_instance_ProgressBar(self): - self.assertTrue(isclass(totest.ProgressBar)) + assert isclass(totest.ProgressBar) def test_instance_data_download(self): - self.assertTrue(isroutine(totest.data_download)) + assert isroutine(totest.data_download) -class TestValues(unittest.TestCase): +class TestValues: # check that the values fit def test_value_PATH_TO_POSYDON_DATA(self): - self.assertIn("POSYDON_data", totest.PATH_TO_POSYDON_DATA) + assert "POSYDON_data" in totest.PATH_TO_POSYDON_DATA def test_value_file(self): - self.assertIn("POSYDON_data.tar.gz", totest.file) + assert "POSYDON_data.tar.gz" in totest.file def test_value_data_url(self): - self.assertEqual("https://zenodo.org/record/6655751/files/" - "POSYDON_data.tar.gz", totest.data_url) + assert totest.data_url == "https://zenodo.org/record/6655751/files/"+\ + "POSYDON_data.tar.gz" def test_value_original_md5(self): - self.assertIn("8873544d9a568ebb85bccffbf1bdcd99", totest.original_md5) + assert "8873544d9a568ebb85bccffbf1bdcd99" in totest.original_md5 + + +class TestFunctions: + @fixture + def test_path(self, tmp_path): + # a temporary path to POSYDON_data for testing + return totest.os.path.join(tmp_path, "POSYDON_data") + + @fixture + def download_statement(self): + # statement that the download started + return "Downloading POSYDON data from Zenodo to PATH_TO_POSYDON_DATA=" + @fixture + def failed_MD5_statement(self): + # statement that MD5 verfication failed + return "Failed to read the tar.gz file for MD5 verificaton" + + @fixture + def extraction_statement(self): + # statement that the tar extraction started + return "Extracting POSYDON data from tar file..." + + @fixture + def removal_statement(self): + # statement that the tar file gets removed + return "Removed downloaded tar file." -class TestFunctions(unittest.TestCase): # test functions - def test_data_download(self): + def test_data_download(self, capsys, monkeypatch, test_path,\ + download_statement, failed_MD5_statement,\ + extraction_statement, removal_statement): + def mock_urlretrieve(url, filename=None, reporthook=None, data=None): + return None + class mock_TarFile: + def getmembers(self): + return [] + def extract(self, member, path='', set_attrs=True, *,\ + numeric_owner=False, filter=None): + return + class mock_open: + def __init__(self, name=None, mode='r', fileobj=None,\ + bufsize=10240, **kwargs): + pass + def __enter__(self): + return mock_TarFile() + def __exit__(self, exc_type, exc_value, exc_traceback): + return False + def mock_urlretrieve2(url, filename=None, reporthook=None, data=None): + totest.os.mkdir(test_path) + if (isinstance(filename, str) and (len(filename)>=8)): + test_ID = filename[-8] + else: + test_ID = "" + with open(totest.os.path.join(test_path, "test"+test_ID+".txt"),\ + "w") as test_file: + test_file.write("Unit Test\n") + with chdir(totest.os.path.join(test_path,"..")): + totest.os.system("tar -czf POSYDON_data"+test_ID+\ + ".tar.gz POSYDON_data") + rmtree(test_path) + return None + # bad input - with self.assertRaises(TypeError): + with raises(TypeError, match="path should be string, bytes, "+\ + "os.PathLike or integer"): totest.data_download(file={}) - with redirect_stdout(StringIO()) as print_out: - totest.data_download(file="./", verbose=True) - self.assertEqual("POSYDON data alraedy exists at ./\n", - print_out.getvalue()) - - @unittest.skipUnless(DO_DOWNLOAD, "Usually, skip the test on the actual " - "download.") - def test_data_download(self): - # real download: may add skip option - tmp_path = "./tmp" - test_path = totest.os.path.join(tmp_path, "POSYDON_data") - if not totest.os.path.exists(test_path): - totest.os.makedirs(test_path) - totest.PATH_TO_POSYDON_DATA = test_path - with redirect_stdout(StringIO()) as print_out: + totest.data_download(file="./") + assert capsys.readouterr().out == "" + totest.data_download(file="./", verbose=True) + assert capsys.readouterr().out == "POSYDON data alraedy exists at ./\n" + # skip real download: do nothing instead + with monkeypatch.context() as mp: + mp.setattr(totest.urllib.request, "urlretrieve", mock_urlretrieve) + mp.setattr(totest.tarfile, "open", mock_open) + # mocked download: fails MD5check, no tar file to remove totest.data_download(file=test_path+".tar.gz", verbose=True) - self.assertIn("Downloading POSYDON data from Zenodo to " - f"PATH_TO_POSYDON_DATA={test_path}", - print_out.getvalue()) - self.assertIn("MD5 verified.", print_out.getvalue()) - self.assertIn("Extracting POSYDON data from tar file...", - print_out.getvalue()) - self.assertIn("Removed downloaded tar file.", print_out.getvalue()) - with self.assertRaises(FileNotFoundError): - totest.original_md5 += '_' - with redirect_stdout(StringIO()) as print_out: - totest.data_download(file=test_path+".tar.gz", verbose=True) - if totest.original_md5[-1]=='_': - totest.original_md5 = totest.original_md5[:-1] - self.assertIn("Failed to read the tar.gz file for MD5 verificaton, " - "cannot guarantee file integrity (this error seems to " - "happen only on macOS).", print_out.getvalue()) - rmtree(tmp_path) - - -class TestProgressBar(unittest.TestCase): + captured_output = capsys.readouterr() + assert download_statement in captured_output.out + assert failed_MD5_statement in captured_output.out + assert extraction_statement in captured_output.out + assert removal_statement not in captured_output.out + # without MD5 check + totest.data_download(file=test_path+".tar.gz", MD5_check=False) + captured_output = capsys.readouterr() + assert download_statement in captured_output.out + assert failed_MD5_statement not in captured_output.out + assert extraction_statement in captured_output.out + assert removal_statement not in captured_output.out + # skip real download: create mock file instead + with monkeypatch.context() as mp: + mp.setattr(totest.urllib.request, "urlretrieve", mock_urlretrieve2) + # mocked download: fails MD5check, which removes tar file and + # causes a FileNotFoundError + with raises(FileNotFoundError, match="No such file or directory:"): + totest.data_download(file=test_path+"0.tar.gz", verbose=True) + captured_output = capsys.readouterr() + assert download_statement in captured_output.out + assert failed_MD5_statement in captured_output.out + assert extraction_statement in captured_output.out + assert removal_statement not in captured_output.out + # return expected hash for testfile + with patch("hashlib.md5") as p_md5: + p_md5.return_value.hexdigest.return_value = totest.original_md5 + totest.data_download(file=test_path+"1.tar.gz") + captured_output = capsys.readouterr() + assert download_statement in captured_output.out + assert failed_MD5_statement not in captured_output.out + assert extraction_statement in captured_output.out + assert removal_statement not in captured_output.out + assert totest.os.path.exists(test_path) + assert totest.os.path.exists(totest.os.path.join(test_path,\ + "test1.txt")) + rmtree(test_path) # removed extracted data + # with verification output + totest.data_download(file=test_path+"2.tar.gz", verbose=True) + captured_output = capsys.readouterr() + assert download_statement in captured_output.out + assert "MD5 verified" in captured_output.out + assert extraction_statement in captured_output.out + assert removal_statement in captured_output.out + assert totest.os.path.exists(test_path) + assert totest.os.path.exists(totest.os.path.join(test_path,\ + "test2.txt")) + rmtree(test_path) # removed extracted data + + +class TestProgressBar: + @fixture + def ProgressBar(self): + # initialize an instance of the class with defaults + ProgressBar = totest.ProgressBar() + return ProgressBar + # test the ProgressBar class - def setUp(self): - # initialize an instance of the class for each test - self.ProgressBar = totest.ProgressBar() - - def tearDown(self): - def finish_remove(pbar): - # finish progress and remove the bar - pbar.finish(end='\r' + ' ' * pbar.term_width + '\r') - - # finish, remove, and reset pbar if needed - if self.ProgressBar.pbar: - finish_remove(self.ProgressBar.pbar) - self.ProgressBar.pbar = None - - def test_init(self): - self.assertTrue(isroutine(self.ProgressBar.__init__)) + def test_init(self, ProgressBar): + assert isroutine(ProgressBar.__init__) # check that the instance is of correct type and all code in the # __init__ got executed: the elements are created and initialized - self.assertIsInstance(self.ProgressBar, totest.ProgressBar) - self.assertIsNone(self.ProgressBar.pbar) - self.assertIsInstance(self.ProgressBar.widgets, list) + assert isinstance(ProgressBar, totest.ProgressBar) + assert ProgressBar.pbar is None + assert isinstance(ProgressBar.widgets, list) - def test_call(self): - self.assertTrue(isroutine(self.ProgressBar.__call__)) + def test_call(self, ProgressBar): + assert isroutine(ProgressBar.__call__) # missing argument - with self.assertRaises(TypeError): - self.ProgressBar() + with raises(TypeError, match="missing 3 required positional "+\ + "arguments: 'block_num', 'block_size', and 'total_size'"): + ProgressBar() # bad input - with self.assertRaises(TypeError): - self.ProgressBar("Test", 1, 1) + with raises(TypeError, match="'<' not supported between instances of"+\ + " 'str' and 'int'"): + ProgressBar("Test", 1, 1) # the progressbar starts before the error, hence tearDown - self.tearDown() - with self.assertRaises(TypeError): - self.ProgressBar(1, "Test", 1) + ProgressBar.pbar = None + with raises(TypeError, match="'<' not supported between instances of"+\ + " 'str' and 'int'"): + ProgressBar(1, "Test", 1) # the progressbar starts before the error, hence tearDown - self.tearDown() - with self.assertRaises(TypeError): - self.ProgressBar(1, 1, "Test") - self.ProgressBar(1, 1, 2) - self.assertAlmostEqual(self.ProgressBar.pbar.percentage, 50.0) - - -if __name__ == "__main__": - unittest.main() + ProgressBar.pbar = None + with raises(TypeError, match="'>' not supported between instances of"+\ + " 'int' and 'str'"): + ProgressBar(1, 1, "Test") + ProgressBar.pbar = None + for i in range(9): + ProgressBar(i, 1, 8) + assert ProgressBar.pbar.percentage==approx(i*12.5) + ProgressBar(9, 1, 8) + assert ProgressBar.pbar.percentage==approx(100.0) diff --git a/posydon/unit_tests/utils/test_posydonwarning.py b/posydon/unit_tests/utils/test_posydonwarning.py index 5366d7162..31d5abfe0 100644 --- a/posydon/unit_tests/utils/test_posydonwarning.py +++ b/posydon/unit_tests/utils/test_posydonwarning.py @@ -10,7 +10,7 @@ # import other needed code for the tests, which is not already imported in the # module you like to test -from pytest import fixture, raises, warns, mark +from pytest import fixture, raises, warns from inspect import isclass, isroutine # define test classes collecting several test functions @@ -509,7 +509,7 @@ def _Caught_POSYDON_Warnings(self, clear_registry): @fixture def test_dict(self): - # a dictionary as a registry for testing + # a dictionary as a registry for testing return {'Unit': "Test"} # test the _Caught_POSYDON_Warnings class From 29cc609afbc4b0d2f0b4ae59bbdf4c6b7421b7fc Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 15 Oct 2024 17:55:07 +0200 Subject: [PATCH 15/34] add configfile.py --- posydon/unit_tests/utils/test_configfile.py | 293 ++++++++++++++++++++ posydon/utils/configfile.py | 15 +- 2 files changed, 301 insertions(+), 7 deletions(-) create mode 100644 posydon/unit_tests/utils/test_configfile.py diff --git a/posydon/unit_tests/utils/test_configfile.py b/posydon/unit_tests/utils/test_configfile.py new file mode 100644 index 000000000..726c24022 --- /dev/null +++ b/posydon/unit_tests/utils/test_configfile.py @@ -0,0 +1,293 @@ +"""Unit tests of posydon/utils/configfile.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the module which will be tested +import posydon.utils.configfile as totest + +# import other needed code for the tests, which is not already imported in the +# module you like to test +from pytest import fixture, raises, warns +from inspect import isclass, isroutine +from ast import AST, parse + +# to check and remove +import unittest + +# define test classes collecting several test functions +class TestElements: + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['ConfigFile', 'VariableKey', '__authors__', '__builtins__', + '__cached__', '__doc__', '__file__', '__loader__', + '__name__', '__package__', '__spec__', 'ast', + 'configparser', 'copy', 'json', 'np', 'operator', 'os', + 'parse_inifile'] + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." + + def test_instance_ConfigFile(self): + assert isclass(totest.ConfigFile) + + def test_instance_parse_inifile(self): + assert isroutine(totest.parse_inifile) + + def test_instance_VariableKey(self): + assert isclass(totest.VariableKey) + + +class TestFunctions: + @fixture + def test_path(self, tmp_path): + # a temporary path for testing + return totest.os.path.join(tmp_path, "test.ini") + + @fixture + def test_ini(self, test_path): + # a temporary ini file for testing" + with open(test_path, "w") as test_file: + test_file.write("[run_parameters]\n") + test_file.write("test_bool1 = True\n") + test_file.write("test_bool2 = False\n") + test_file.write("test_None = None\n") + test_file.write("\n[mesa_inlists]\n") + test_file.write("test_float = 0.1\n") + test_file.write("test_int = 10\n") + test_file.write("test_list = ['Unit Test1', 'Unit Test2']\n") + test_file.write("test_str1 = 'Unit Test'\n") + test_file.write("test_str2 = 'Unit,Test'\n") + test_file.write("test_BinOp = ${test_int}+1\n") + test_file.write("\n[mesa_extras]\n") + test_file.write("test_else = print('1')\n") + test_file.write("\n[slurm]\n") + test_file.write("test_exception = ast.parse('X=1', mode='eval')\n") + return + + # test functions + def test_parse_inifile(self, test_path, test_ini, monkeypatch): + def mock_parse(source, filename='', mode='exec', *,\ + type_comments=False, feature_version=None, optimize=-1): + mock_node = AST() + mock_node.body = parse(source, mode='eval') + return mock_node + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'inifile'"): + totest.parse_inifile() + # bad input + with raises(TypeError, match="'int' object is not iterable"): + totest.parse_inifile(1) + # read test ini + rp, s, mi, me = totest.parse_inifile(test_path) + assert rp == {'MESA_DIR'.lower(): totest.os.environ['MESA_DIR'],\ + 'test_bool1'.lower(): True, 'test_bool2'.lower(): False,\ + 'test_None'.lower(): None} + assert s == {'MESA_DIR'.lower(): totest.os.environ['MESA_DIR'],\ + 'test_exception'.lower():\ + ["ast.parse('X=1'", "mode='eval')"]} + assert mi == {'MESA_DIR'.lower(): totest.os.environ['MESA_DIR'],\ + 'test_float'.lower(): 0.1, 'test_int'.lower(): 10,\ + 'test_list'.lower(): ['Unit Test1', 'Unit Test2'],\ + 'test_str1'.lower(): 'Unit Test',\ + 'test_str2'.lower(): ['Unit', 'Test'],\ + 'test_BinOp'.lower(): 11} + assert me == {'MESA_DIR'.lower(): totest.os.environ['MESA_DIR'],\ + 'test_else'.lower(): "print('1')"} + with monkeypatch.context() as mp: + # replace parse to check iteration + mp.setattr(totest.ast, "parse", mock_parse) + # replace content of test.ini to check Error + with open(test_path, "w") as test_file: + test_file.write("[Unit Test]\n") + test_file.write("test_value = 'Test'\n") + with raises(KeyError, match="'run_parameters'"): + totest.parse_inifile(test_path) + + +class TestConfigFile: + @fixture + def ConfigFile(self): + # initialize an instance of the class with defaults + ConfigFile = totest.ConfigFile() + return ConfigFile + + @fixture + def test_path(self, tmp_path): + # a temporary path for testing + return totest.os.path.join(tmp_path, "test.json") + + @fixture + def test_json(self, test_path): + # a temporary ini file for testing" + with open(test_path, "w") as test_file: + test_file.write('{\n "Unit": "Test"\n}\n') + return + + @fixture + def test_entries(self): + # entries for testing + return {'Unit1': "Test", 'Unit2': "Again"} + + @fixture + def ConfigFile_filled(self, test_entries): + # initialize an instance of the class with defaults + ConfigFile = totest.ConfigFile() + ConfigFile.update(test_entries) + return ConfigFile + + # test the ConfigFile class + def test_init(self, ConfigFile, tmp_path): + assert isroutine(ConfigFile.__init__) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + assert isinstance(ConfigFile, totest.ConfigFile) + assert ConfigFile.entries == {} + assert ConfigFile.path is None + # error on directory + with raises(IsADirectoryError, match="Is a directory: '"+\ + str(tmp_path)+"'"): + totest.ConfigFile(path=tmp_path) + # non-existing path + test_path = totest.os.path.join(tmp_path, "does_not_exist.test") + test_ConfigFile = totest.ConfigFile(test_path) + assert test_ConfigFile.path == test_path + + def test_deepcopy(self, ConfigFile): + assert isroutine(ConfigFile.deepcopy) + ConfigFile.entries['Unit'] = "Test" + ConfigFile.path = "Test_path" + test_ConfigFile = ConfigFile.deepcopy() + # not same object + assert test_ConfigFile is not ConfigFile + # but same content + assert test_ConfigFile.entries == ConfigFile.entries + assert test_ConfigFile.path == ConfigFile.path + + def test_serialize(self, ConfigFile): + assert isroutine(ConfigFile._serialize) + # serialize numpy array + np_array = totest.np.array([2, 1, 0]) + assert ConfigFile._serialize(data=np_array) == [2, 1, 0] + # other data don't give a return + assert ConfigFile._serialize(data="Test") is None + + def test_save(self, ConfigFile, tmp_path): + assert isroutine(ConfigFile.save) + # bad input + with raises(ValueError, match="No path passed."): + ConfigFile.save() + with raises(PermissionError, match="JSON file not saved: overwrite "+\ + "not permitted."): + ConfigFile.save(path=tmp_path, overwrite=False) + test_path = totest.os.path.join(tmp_path, "save.test") + ConfigFile.path = test_path + ConfigFile.entries['Unit'] = "Test" + ConfigFile.save() + assert totest.os.path.isfile(test_path) + + def test_load(self, ConfigFile, test_path, test_json): + assert isroutine(ConfigFile.load) + # bad input + with raises(ValueError, match="No path passed."): + ConfigFile.load() + # load mock file with path as keyword + ConfigFile.load(path=test_path) + assert ConfigFile.entries == {'Unit': "Test"} + # set path and entries + ConfigFile.path = test_path + ConfigFile.entries['Unit'] = "ToReplace" + # try to load mock file without permission to overwrite + with raises(PermissionError,\ + match="Not allowed to update the entries"): + ConfigFile.load() + assert ConfigFile.entries == {'Unit': "ToReplace"} + # load mock file and overwrite entry + ConfigFile.load(can_update=True) + assert ConfigFile.entries == {'Unit': "Test"} + + def test_getattr(self, ConfigFile): + assert isroutine(ConfigFile.__getattr__) + # bad input + with raises(KeyError, match='Test'): + ConfigFile.Test + # add a value to entries and get it + ConfigFile.entries['Unit'] = "Test" + assert ConfigFile.Unit == "Test" + + def test_getitem(self, ConfigFile): + assert isroutine(ConfigFile.__getitem__) + # bad input + with raises(KeyError, match='Test'): + ConfigFile['Test'] + # add a value to entries and get it + ConfigFile.entries['Unit'] = "Test" + assert ConfigFile['Unit'] == "Test" + + def test_setitem(self, ConfigFile): + assert isroutine(ConfigFile.__setitem__) + # add a value to entries and get it + ConfigFile['Unit'] = "Test" + assert ConfigFile.entries['Unit'] == "Test" + + def test_delitem(self, ConfigFile_filled): + assert isroutine(ConfigFile_filled.__delitem__) + with raises(KeyError, match="'Unit'"): + del ConfigFile_filled['Unit'] + # delete entries + del ConfigFile_filled['Unit1'] + del ConfigFile_filled['Unit2'] + assert ConfigFile_filled.entries == {} + + def test_iter(self, ConfigFile_filled, test_entries): + assert isroutine(ConfigFile_filled.__iter__) + # loop over entries + iteration = 0 + for key in ConfigFile_filled: + iteration += 1 + assert ConfigFile_filled.entries[key] == test_entries[key] + # check that it was iterated over all keys in test_entries + assert iteration == len(test_entries) + + def test_update(self, ConfigFile, test_entries): + assert isroutine(ConfigFile.update) + ConfigFile.update(test_entries) + assert ConfigFile.entries == test_entries + + def test_keys(self, ConfigFile_filled, test_entries): + assert isroutine(ConfigFile_filled.keys) + assert ConfigFile_filled.keys() == test_entries.keys() + + def test_values(self, ConfigFile_filled, test_entries): + assert isroutine(ConfigFile_filled.values) + assert isinstance(ConfigFile_filled.values(),\ + type(test_entries.values())) + iteration = 0 + for v in ConfigFile_filled.values(): + iteration += 1 + assert v in test_entries.values() + # check that it was iterated over all values in test_entries + assert iteration == len(test_entries.values()) + + def test_items(self, ConfigFile_filled, test_entries): + assert isroutine(ConfigFile_filled.items) + assert ConfigFile_filled.items() == test_entries.items() + + def test_repr(self, ConfigFile_filled): + assert isroutine(ConfigFile_filled.__repr__) + assert repr(ConfigFile_filled) == "Unit1: Test\nUnit2: Again\n" + + def test_contains(self, ConfigFile_filled, test_entries): + assert isroutine(ConfigFile_filled.__contains__) + for key in test_entries: + assert key in ConfigFile_filled + + def test_len(self, ConfigFile_filled, test_entries): + assert isroutine(ConfigFile_filled.__len__) + assert len(ConfigFile_filled) == len(test_entries) + + +#class TestVariableKey: +# # test the VariableKey class: looks to be not used as long as using python3 diff --git a/posydon/utils/configfile.py b/posydon/utils/configfile.py index 99b484982..92bbac4ef 100644 --- a/posydon/utils/configfile.py +++ b/posydon/utils/configfile.py @@ -133,11 +133,12 @@ def save(self, path=None, overwrite=True): """ if path is None: if self.path is None: - raise Exception("No path passed.") + raise ValueError("No path passed.") path = self.path if os.path.exists(path) and not overwrite: - raise Exception("JSON file not saved: overwrite not permitted.") + raise PermissionError("JSON file not saved: overwrite not " + "permitted.") with open(path, "wt") as f: json.dump(self.entries, f, sort_keys=True, indent=4, @@ -158,7 +159,7 @@ def load(self, path=None, can_update=False): """ if path is None: if self.path is None: - raise Exception("No path passed.") + raise ValueError("No path passed.") path = self.path with open(path, "rt") as f: @@ -169,8 +170,8 @@ def load(self, path=None, can_update=False): new_keys = set(new_entries.keys()) common = list(current_keys & new_keys) if len(common) != 0: - raise Exception("Not allowed to update the entries {}". - format(common)) + raise PermissionError("Not allowed to update the entries" + " {}".format(common)) self.entries.update(new_entries) @@ -256,7 +257,7 @@ def _eval(node): _eval(node.right)) elif isinstance(node, ast.List): return [_eval(x) for x in node.elts] - elif isinstance(node, ast.Name): + elif isinstance(node, ast.Name): # pragma: no cover result = VariableKey(item=node) constants_lookup = { 'True': True, @@ -311,7 +312,7 @@ def _eval(node): return run_parameters, slurm, mesa_inlists, mesa_extras -class VariableKey(object): +class VariableKey(object): # pragma: no cover """A dictionary key which is a variable. @ivar item: The variable AST object. From e547b643cff8f9998f417cced8f22f1cb847f541 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 15 Oct 2024 17:58:19 +0200 Subject: [PATCH 16/34] cleanup --- posydon/unit_tests/utils/test_configfile.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/posydon/unit_tests/utils/test_configfile.py b/posydon/unit_tests/utils/test_configfile.py index 726c24022..5505cbc35 100644 --- a/posydon/unit_tests/utils/test_configfile.py +++ b/posydon/unit_tests/utils/test_configfile.py @@ -10,13 +10,10 @@ # import other needed code for the tests, which is not already imported in the # module you like to test -from pytest import fixture, raises, warns +from pytest import fixture, raises from inspect import isclass, isroutine from ast import AST, parse -# to check and remove -import unittest - # define test classes collecting several test functions class TestElements: # check for objects, which should be an element of the tested module From 42eae138b06baf2d760899b664904f65d57d804b Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:15:06 -0500 Subject: [PATCH 17/34] Create test_installation.py using this as a simple example to try out automated testing framework --- posydon/unit_tests/test_installation.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 posydon/unit_tests/test_installation.py diff --git a/posydon/unit_tests/test_installation.py b/posydon/unit_tests/test_installation.py new file mode 100644 index 000000000..d820484d9 --- /dev/null +++ b/posydon/unit_tests/test_installation.py @@ -0,0 +1,6 @@ +import subprocess + +def test_installation(): + """Test that POSYDON installs successfully.""" + result = subprocess.run(["pip", "install", "."], capture_output=True, text=True) + assert result.returncode == 0, f"Installation failed: {result.stderr}" From 3379d0bec509a913d9b8f35864e223889e682d17 Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:31:22 -0500 Subject: [PATCH 18/34] Create continuous_integration.yml Automatic trigger for tests (currently just installation but can include any combination) for PRs created to main or development. Tests on ubuntu, mac, and windows environments. Currently just testing python 3.11 but can add other versions if desired. --- .github/workflows/continuous_integration.yml | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/continuous_integration.yml diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml new file mode 100644 index 000000000..147a7768d --- /dev/null +++ b/.github/workflows/continuous_integration.yml @@ -0,0 +1,35 @@ +name: POSYDON Continuous Integration + +on: + pull_request: + branches: [main, development] + +jobs: + test: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.11'] # can add more if we want to support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Normalize line endings on Windows + if: runner.os == 'Windows' + run: git config --global core.autocrlf false + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install pytest + run: python -m pip install --upgrade pip && pip install pytest + + - name: Run installation test + run: | + pytest unit_tests/test_installation.py + # can remove ref to specific test to run all tests in unit_tests From d132bf5c5de9eb5e106b4b78b255945da8f1278d Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:35:13 -0500 Subject: [PATCH 19/34] Update continuous_integration.yml fixed path to installation test file --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 147a7768d..ec87c0a8c 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -31,5 +31,5 @@ jobs: - name: Run installation test run: | - pytest unit_tests/test_installation.py + pytest posydon/unit_tests/test_installation.py # can remove ref to specific test to run all tests in unit_tests From e64b79e508c1c04d5e2f635e35c632ed68d1a658 Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:40:41 -0500 Subject: [PATCH 20/34] Update continuous_integration.yml making mac the first test out of the three OSes --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index ec87c0a8c..f656b72a1 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [macos-latest, ubuntu-latest, windows-latest] python-version: ['3.11'] # can add more if we want to support steps: From 8d51eeee74c3114e7b9864ce02fe26f52ed31ef9 Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:51:08 -0500 Subject: [PATCH 21/34] Delete posydon/unit_tests/test_installation.py realized this could just be included in the github workflow --- posydon/unit_tests/test_installation.py | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 posydon/unit_tests/test_installation.py diff --git a/posydon/unit_tests/test_installation.py b/posydon/unit_tests/test_installation.py deleted file mode 100644 index d820484d9..000000000 --- a/posydon/unit_tests/test_installation.py +++ /dev/null @@ -1,6 +0,0 @@ -import subprocess - -def test_installation(): - """Test that POSYDON installs successfully.""" - result = subprocess.run(["pip", "install", "."], capture_output=True, text=True) - assert result.returncode == 0, f"Installation failed: {result.stderr}" From 515be689088d94b8e3cff0d0ed8b5e3c9ca2558b Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:54:07 -0500 Subject: [PATCH 22/34] Update continuous_integration.yml changed to include full posydon installation step to remove redundancy. Currently including all extras in the setup.py but can remove any if needed. --- .github/workflows/continuous_integration.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index f656b72a1..2dc66552f 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -26,10 +26,11 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install pytest - run: python -m pip install --upgrade pip && pip install pytest - - - name: Run installation test + - name: Install POSYDON with extras run: | - pytest posydon/unit_tests/test_installation.py - # can remove ref to specific test to run all tests in unit_tests + python -m pip install --upgrade pip + pip install ".[doc,vis,ml,hpc]" + + # - name: Run all tests + # run: | + # pytest posydon/unit_tests/ From eed7c90e09ce44366ceec15e58e5d10d01c05899 Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:56:16 -0500 Subject: [PATCH 23/34] Update continuous_integration.yml removing extras from the every-PR test. I'll move them to a weekly test instead. --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 2dc66552f..2377e3bf4 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -29,7 +29,7 @@ jobs: - name: Install POSYDON with extras run: | python -m pip install --upgrade pip - pip install ".[doc,vis,ml,hpc]" + pip install . # - name: Run all tests # run: | From 87a8fece4405d23987a3146093f9b9852f613db1 Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:57:58 -0500 Subject: [PATCH 24/34] Create install_extras.yml scheduled cron job testing installation of setup.py extras, every sunday --- .github/workflows/install_extras.yml | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/install_extras.yml diff --git a/.github/workflows/install_extras.yml b/.github/workflows/install_extras.yml new file mode 100644 index 000000000..eb8c2edcf --- /dev/null +++ b/.github/workflows/install_extras.yml @@ -0,0 +1,32 @@ +name: Testing installation of extras in setup.py + +on: + schedule: + - cron: "0 0 * * 0" # Runs at 00:00 (UTC) every Sunday + +jobs: + test: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + python-version: ['3.11'] # can add more if we want to support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Normalize line endings on Windows + if: runner.os == 'Windows' + run: git config --global core.autocrlf false + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install POSYDON with extras + run: | + python -m pip install --upgrade pip + pip install ".[doc,vis,ml,hpc]" From 00342222c873dbba50310267f88d01d0c2e7a7e1 Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:58:14 -0500 Subject: [PATCH 25/34] Update continuous_integration.yml --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 2377e3bf4..92eb1741e 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -26,7 +26,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install POSYDON with extras + - name: Install POSYDON without extras run: | python -m pip install --upgrade pip pip install . From cedd0ce443b68b2880a1a1bb56b791a796b78ea1 Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:07:13 -0500 Subject: [PATCH 26/34] Delete .github/workflows/install_extras.yml --- .github/workflows/install_extras.yml | 32 ---------------------------- 1 file changed, 32 deletions(-) delete mode 100644 .github/workflows/install_extras.yml diff --git a/.github/workflows/install_extras.yml b/.github/workflows/install_extras.yml deleted file mode 100644 index eb8c2edcf..000000000 --- a/.github/workflows/install_extras.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Testing installation of extras in setup.py - -on: - schedule: - - cron: "0 0 * * 0" # Runs at 00:00 (UTC) every Sunday - -jobs: - test: - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [macos-latest, ubuntu-latest, windows-latest] - python-version: ['3.11'] # can add more if we want to support - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Normalize line endings on Windows - if: runner.os == 'Windows' - run: git config --global core.autocrlf false - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install POSYDON with extras - run: | - python -m pip install --upgrade pip - pip install ".[doc,vis,ml,hpc]" From 6d84253bda190c1012b0aaf0abb433672aa936ce Mon Sep 17 00:00:00 2001 From: Elizabeth Teng <41969755+elizabethteng@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:07:21 -0500 Subject: [PATCH 27/34] Delete .github/workflows/continuous_integration.yml --- .github/workflows/continuous_integration.yml | 36 -------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/continuous_integration.yml diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml deleted file mode 100644 index 92eb1741e..000000000 --- a/.github/workflows/continuous_integration.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: POSYDON Continuous Integration - -on: - pull_request: - branches: [main, development] - -jobs: - test: - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [macos-latest, ubuntu-latest, windows-latest] - python-version: ['3.11'] # can add more if we want to support - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Normalize line endings on Windows - if: runner.os == 'Windows' - run: git config --global core.autocrlf false - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install POSYDON without extras - run: | - python -m pip install --upgrade pip - pip install . - - # - name: Run all tests - # run: | - # pytest posydon/unit_tests/ From 7d65614ef1ddf6bbf2116bdf028fb5041eabeb14 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Fri, 1 Nov 2024 18:55:18 +0100 Subject: [PATCH 28/34] add test_gridutils.py --- posydon/unit_tests/utils/test_configfile.py | 14 +- posydon/unit_tests/utils/test_constants.py | 29 +- .../unit_tests/utils/test_data_download.py | 10 +- posydon/unit_tests/utils/test_gridutils.py | 760 ++++++++++++++++++ posydon/unit_tests/utils/test_ignorereason.py | 4 +- .../utils/test_limits_thresholds.py | 20 +- posydon/unit_tests/utils/test_posydonerror.py | 10 +- .../unit_tests/utils/test_posydonwarning.py | 28 +- posydon/utils/gridutils.py | 6 +- 9 files changed, 823 insertions(+), 58 deletions(-) create mode 100644 posydon/unit_tests/utils/test_gridutils.py diff --git a/posydon/unit_tests/utils/test_configfile.py b/posydon/unit_tests/utils/test_configfile.py index 5505cbc35..cdb7f95f6 100644 --- a/posydon/unit_tests/utils/test_configfile.py +++ b/posydon/unit_tests/utils/test_configfile.py @@ -18,11 +18,11 @@ class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): - elements = ['ConfigFile', 'VariableKey', '__authors__', '__builtins__', - '__cached__', '__doc__', '__file__', '__loader__', - '__name__', '__package__', '__spec__', 'ast', - 'configparser', 'copy', 'json', 'np', 'operator', 'os', - 'parse_inifile'] + elements = ['ConfigFile', 'VariableKey', '__authors__',\ + '__builtins__', '__cached__', '__doc__', '__file__',\ + '__loader__', '__name__', '__package__', '__spec__',\ + 'ast', 'configparser', 'copy', 'json', 'np', 'operator',\ + 'os', 'parse_inifile'] assert dir(totest) == elements, "There might be added or removed "+\ "objects without an update on the unit test." @@ -44,7 +44,7 @@ def test_path(self, tmp_path): @fixture def test_ini(self, test_path): - # a temporary ini file for testing" + # a temporary ini file for testing with open(test_path, "w") as test_file: test_file.write("[run_parameters]\n") test_file.write("test_bool1 = True\n") @@ -118,7 +118,7 @@ def test_path(self, tmp_path): @fixture def test_json(self, test_path): - # a temporary ini file for testing" + # a temporary json file for testing with open(test_path, "w") as test_file: test_file.write('{\n "Unit": "Test"\n}\n') return diff --git a/posydon/unit_tests/utils/test_constants.py b/posydon/unit_tests/utils/test_constants.py index 3db818b0e..e8fef8a48 100644 --- a/posydon/unit_tests/utils/test_constants.py +++ b/posydon/unit_tests/utils/test_constants.py @@ -16,20 +16,21 @@ class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): - elements = ['H_weight', 'He_weight', 'Lsun', 'Lsun33', 'Msun', - 'Msun33', 'Qconv', 'Rsun', 'Rsun11', 'SNcheck_ERR', - 'Teffsol', 'Zsun', '__authors__', '__builtins__', - '__cached__', '__doc__', '__file__', '__loader__', - '__name__', '__package__', '__spec__', 'a2rad', - 'age_of_universe', 'agesol', 'amu', 'asol', 'au', 'aursun', - 'avo', 'boltz_sigma', 'boltzm', 'cgas', 'clight', 'crad', - 'day2sec', 'dayyer', 'ev2erg', 'fine', 'hbar', 'hion', - 'inversecm2erg', 'kerg', 'kev', 'km2cm', 'loggsol', 'lsol', - 'ly', 'm_earth', 'm_jupiter', 'mbolsol', 'mbolsun', 'me', - 'mev_amu', 'mev_to_ergs', 'mn', 'mp', 'msol', 'pc', 'pi', - 'planck_h', 'qe', 'r_earth', 'r_jupiter', 'rad2a', 'rbohr', - 'rhonuc', 'rsol', 'secyer', 'semimajor_axis_jupiter', - 'ssol', 'standard_cgrav', 'weinfre', 'weinlam'] + elements = ['H_weight', 'He_weight', 'Lsun', 'Lsun33', 'Msun',\ + 'Msun33', 'Qconv', 'Rsun', 'Rsun11', 'SNcheck_ERR',\ + 'Teffsol', 'Zsun', '__authors__', '__builtins__',\ + '__cached__', '__doc__', '__file__', '__loader__',\ + '__name__', '__package__', '__spec__', 'a2rad',\ + 'age_of_universe', 'agesol', 'amu', 'asol', 'au',\ + 'aursun', 'avo', 'boltz_sigma', 'boltzm', 'cgas',\ + 'clight', 'crad', 'day2sec', 'dayyer', 'ev2erg', 'fine',\ + 'hbar', 'hion', 'inversecm2erg', 'kerg', 'kev', 'km2cm',\ + 'loggsol', 'lsol', 'ly', 'm_earth', 'm_jupiter',\ + 'mbolsol', 'mbolsun', 'me', 'mev_amu', 'mev_to_ergs',\ + 'mn', 'mp', 'msol', 'pc', 'pi', 'planck_h', 'qe',\ + 'r_earth', 'r_jupiter', 'rad2a', 'rbohr', 'rhonuc',\ + 'rsol', 'secyer', 'semimajor_axis_jupiter', 'ssol',\ + 'standard_cgrav', 'weinfre', 'weinlam'] assert dir(totest) == elements, "There might be added or removed "+\ "objects without an update on the unit test." diff --git a/posydon/unit_tests/utils/test_data_download.py b/posydon/unit_tests/utils/test_data_download.py index 200b40360..e50ef2325 100644 --- a/posydon/unit_tests/utils/test_data_download.py +++ b/posydon/unit_tests/utils/test_data_download.py @@ -20,11 +20,11 @@ class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): - elements = ['PATH_TO_POSYDON_DATA', 'ProgressBar', '__authors__', - '__builtins__', '__cached__', '__doc__', '__file__', - '__loader__', '__name__', '__package__', '__spec__', - 'data_download', 'data_url', 'file', 'hashlib', - 'original_md5', 'os', 'progressbar', 'tarfile', 'tqdm', + elements = ['PATH_TO_POSYDON_DATA', 'ProgressBar', '__authors__',\ + '__builtins__', '__cached__', '__doc__', '__file__',\ + '__loader__', '__name__', '__package__', '__spec__',\ + 'data_download', 'data_url', 'file', 'hashlib',\ + 'original_md5', 'os', 'progressbar', 'tarfile', 'tqdm',\ 'urllib'] assert dir(totest) == elements, "There might be added or removed "+\ "objects without an update on the unit test." diff --git a/posydon/unit_tests/utils/test_gridutils.py b/posydon/unit_tests/utils/test_gridutils.py new file mode 100644 index 000000000..324b8a15a --- /dev/null +++ b/posydon/unit_tests/utils/test_gridutils.py @@ -0,0 +1,760 @@ +"""Unit tests of posydon/utils/gridutils.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the module which will be tested +import posydon.utils.gridutils as totest + +# import other needed code for the tests, which is not already imported in the +# module you like to test +from pytest import fixture, raises, warns, approx +from inspect import isclass, isroutine +from posydon.utils.posydonwarning import MissingFilesWarning + +# define test classes collecting several test functions +class TestElements: + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['LG_MTRANSFER_RATE_THRESHOLD', 'Msun', 'Pwarn', 'Rsun',\ + 'T_merger_P', 'T_merger_a', '__authors__', '__builtins__',\ + '__cached__', '__doc__', '__file__', '__loader__',\ + '__name__', '__package__', '__spec__', 'add_field',\ + 'beta_gw', 'cgrav', 'clean_inlist_file', 'clight',\ + 'convert_output_to_table', 'find_index_nearest_neighbour',\ + 'find_nearest', 'fix_He_core', 'get_cell_edges',\ + 'get_final_proposed_points', 'get_new_grid_name', 'gzip',\ + 'join_lists', 'kepler3_a', 'np', 'os', 'pd',\ + 'read_EEP_data_file', 'read_MESA_data_file', 'secyear'] + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." + + def test_instance_join_lists(self): + assert isroutine(totest.join_lists) + + def test_instance_read_MESA_data_file(self): + assert isroutine(totest.read_MESA_data_file) + + def test_instance_read_EEP_data_file(self): + assert isroutine(totest.read_EEP_data_file) + + def test_instance_fix_He_core(self): + assert isroutine(totest.fix_He_core) + + def test_instance_add_field(self): + assert isroutine(totest.add_field) + + def test_instance_get_cell_edges(self): + assert isroutine(totest.get_cell_edges) + + def test_instance_find_nearest(self): + assert isroutine(totest.find_nearest) + + def test_instance_find_index_nearest_neighbour(self): + assert isroutine(totest.find_index_nearest_neighbour) + + def test_instance_get_final_proposed_points(self): + assert isroutine(totest.get_final_proposed_points) + + def test_instance_T_merger_P(self): + assert isroutine(totest.T_merger_P) + + def test_instance_beta_gw(self): + assert isroutine(totest.beta_gw) + + def test_instance_kepler3_a(self): + assert isroutine(totest.kepler3_a) + + def test_instance_T_merger_a(self): + assert isroutine(totest.T_merger_a) + + def test_instance_convert_output_to_table(self): + assert isroutine(totest.convert_output_to_table) + + def test_instance_clean_inlist_file(self): + assert isroutine(totest.clean_inlist_file) + + def test_instance_get_new_grid_name(self): + assert isroutine(totest.get_new_grid_name) + + +class TestFunctions: + @fixture + def data_path(self, tmp_path): + # a temporary data path for testing + return totest.os.path.join(tmp_path, "history.data") + + @fixture + def no_path(self, tmp_path): + # a path which does not exist for testing + return totest.os.path.join(tmp_path, "does_not_exist.test") + + @fixture + def MESA_data(self): + # mock data + return totest.np.array([(1, 2, 3.3), (1, -2, -3.3)],\ + dtype=[('COL1', '4}".format(v)) + else: + test_file.write(" {:>4}".format(v)) + return + + @fixture + def EEP_data_file(self, data_path, MESA_data): + # a temporary data file for testing + with open(data_path, "w") as test_file: + for i in range(11): + test_file.write("Test HEADER{}\n".format(i+1)) + for k in MESA_data.dtype.names: + test_file.write("{:<5}".format(k)) + for j in range(2): + for i,v in enumerate(MESA_data[j]): + if v==int(v): + v = int(v) + if i==0: + test_file.write("\n{:>4}".format(v)) + else: + test_file.write(" {:>4}".format(v)) + return + + @fixture + def MESA_BH_data(self): + # mock data + return totest.np.array([(1.0, 15.0, 30.0, -5.0),\ + (1.0, 15.0, 30.0, -4.0)],\ + dtype=[('period_days', '17}".format(v)) + else: + test_file.write(" {:>17}".format(v)) + return + + @fixture + def MESA_BH_data2(self): + # mock data + return totest.np.array([(100.0, 15.0, 30.0, -5.0),\ + (100.0, 15.0, 30.0, -4.0)],\ + dtype=[('period_days', '17}".format(v)) + else: + test_file.write(" {:>17}".format(v)) + return + + @fixture + def MESA_SH_data(self): + # mock data + return totest.np.array([(1.0, 4.0, 0.0, 0.0), (2.0, 4.0, 5.0, 4.0)],\ + dtype=[('log_L', '12}".format(v)) + else: + test_file.write(" {:>12}".format(v)) + return + + @fixture + def H2_path(self, tmp_path): + # a temporary star2 history path for testing + return totest.os.path.join(tmp_path, "history2.data") + + @fixture + def H2_file(self, H2_path, MESA_SH_data): + # a temporary star2 history file for testing + with open(H2_path, "w") as test_file: + for i in range(5): + test_file.write("Test HEADER{}\n".format(i+1)) + for k in MESA_SH_data.dtype.names: + test_file.write("{:<13}".format(k)) + for j in range(2): + for i,v in enumerate(MESA_SH_data[j]): + if v==int(v): + v = int(v) + if i==0: + test_file.write("\n{:>12}".format(v)) + else: + test_file.write(" {:>12}".format(v)) + return + + @fixture + def out_path(self, tmp_path): + # a temporary out path for testing + return totest.os.path.join(tmp_path, "out.txt") + + @fixture + def out_file(self, out_path): + # a temporary out file for testing + with open(out_path, "w") as test_file: + for i in range(5): + test_file.write("Test HEADER{}\n".format(i+1)) + return + + @fixture + def out_gz_path(self, tmp_path): + # a temporary out path for testing + return totest.os.path.join(tmp_path, "out_gz.txt") + + @fixture + def out_gz_file(self, out_gz_path): + # a temporary out file for testing + with open(out_gz_path, "w") as test_file: + for i in range(5): + test_file.write("Test HEADER{}\n".format(i+1)) + test_file.write("model is overflowing at ZAMS\n") + totest.os.system(f"gzip -1 {out_gz_path}") + return + + @fixture + def out_path2(self, tmp_path): + # a temporary out path for testing + return totest.os.path.join(tmp_path, "out2.txt") + + @fixture + def out_file2(self, out_path2): + # a temporary out file for testing + with open(out_path2, "w") as test_file: + for i in range(5): + test_file.write("Test HEADER{}\n".format(i+1)) + test_file.write("TURNING ON CE\n") + return + + @fixture + def out_path3(self, tmp_path): + # a temporary out path for testing + return totest.os.path.join(tmp_path, "out3.txt") + + @fixture + def out_file3(self, out_path3): + # a temporary out file for testing + with open(out_path3, "w") as test_file: + for i in range(5): + test_file.write("Test HEADER{}\n".format(i+1)) + test_file.write("Terminate because accretor (r-rl)/rl > "+\ + "accretor_overflow_terminate\n") + return + + @fixture + def out_path4(self, tmp_path): + # a temporary out path for testing + return totest.os.path.join(tmp_path, "out4.txt") + + @fixture + def out_file4(self, out_path4): + # a temporary out file for testing + with open(out_path4, "w") as test_file: + for i in range(5): + test_file.write("Test HEADER{}\n".format(i+1)) + test_file.write("TURNING ON CE\n") + test_file.write("Terminate because accretor (r-rl)/rl > "+\ + "accretor_overflow_terminate\n") + return + + @fixture + def out_path5(self, tmp_path): + # a temporary out path for testing + return totest.os.path.join(tmp_path, "out5.txt") + + @fixture + def out_file5(self, out_path5): + # a temporary out file for testing + with open(out_path5, "w") as test_file: + for i in range(5): + test_file.write("Test HEADER{}\n".format(i+1)) + test_file.write("Terminate due to primary depleting carbon\n") + return + + @fixture + def out_path6(self, tmp_path): + # a temporary out path for testing + return totest.os.path.join(tmp_path, "out6.txt") + + @fixture + def out_file6(self, out_path6): + # a temporary out file for testing + with open(out_path6, "w") as test_file: + for i in range(5): + test_file.write("Test HEADER{}\n".format(i+1)) + test_file.write("TURNING ON CE\n") + test_file.write("Terminate due to primary depleting carbon\n") + return + + @fixture + def ini_path(self, tmp_path): + # a temporary ini path for testing + return totest.os.path.join(tmp_path, "test.ini") + + @fixture + def ini_file(self, ini_path): + # a temporary ini file for testing + with open(ini_path, "w") as test_file: + test_file.write("[TEST INI SECTION]\n") + test_file.write("test_float = 1.0\n") + test_file.write("test_string = 'unit'\n") + test_file.write("!test_comment = 0\n") + return + + @fixture + def ini_path2(self, tmp_path): + # a temporary ini path for testing + return totest.os.path.join(tmp_path, "test2.ini") + + @fixture + def ini_file2(self, ini_path2): + # a temporary ini file for testing + with open(ini_path2, "w") as test_file: + test_file.write("TEST INI SECTION 1&\n") + test_file.write("test_int = 1\n") + test_file.write("TEST INI SECTION 2&\n") + test_file.write("test_empty = ''\n") + return + + # test functions + def test_join_lists(self): + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'A' and 'B'"): + totest.join_lists() + # bad input + with raises(TypeError, match="'int' object is not iterable"): + totest.join_lists(1, []) + with raises(TypeError, match="'int' object is not iterable"): + totest.join_lists([], 1) + # test cases + assert totest.join_lists([1, 2, 3], [4, 5]) == [1, 2, 3, 4, 5] + assert totest.join_lists([1, 2, 3], [4, 1]) == [1, 2, 3, 4] + assert totest.join_lists([1, 2, 1], [4, 1]) == [1, 2, 1, 4] + + def test_read_MESA_data_file(self, no_path, data_path,\ + MESA_data_file, MESA_data): + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'path' and 'columns'"): + totest.read_MESA_data_file() + # path is None or non-existing + assert totest.read_MESA_data_file(None, ['Test']) is None + assert totest.read_MESA_data_file(no_path, ['Test']) is None + # read full test file + assert totest.np.array_equal(MESA_data,\ + totest.read_MESA_data_file(data_path, MESA_data.dtype.names)) + # read columns individually from test file + for k in MESA_data.dtype.names: + assert totest.np.array_equal(MESA_data[[k]],\ + totest.read_MESA_data_file(data_path, [k])) + # read column pairs from test file + k0 = MESA_data.dtype.names[0] + for k in MESA_data.dtype.names: + if k!=k0: + assert totest.np.array_equal(MESA_data[[k0, k]],\ + totest.read_MESA_data_file(data_path, [k0, k])) + # warning for non readable data + with open(data_path, "w") as test_file: + test_file.write(10*"Test\n") + with warns(MissingFilesWarning, match="Problems with reading file "+\ + data_path): + assert totest.read_MESA_data_file(data_path,\ + MESA_data.dtype.names) is None + + def test_read_EEP_data_file(self, no_path, data_path,\ + EEP_data_file, MESA_data): + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'path' and 'columns'"): + totest.read_EEP_data_file() + # path is None or non-existing + assert totest.read_EEP_data_file(None, ['Test']) is None + with warns(MissingFilesWarning, match="Problems with reading file "+\ + no_path): + assert totest.read_EEP_data_file(no_path, ['Test']) is None + # read full test file + assert totest.np.array_equal(MESA_data,\ + totest.read_EEP_data_file(data_path, MESA_data.dtype.names)) + # read columns individually from test file + for k in MESA_data.dtype.names: + assert totest.np.array_equal(MESA_data[[k]],\ + totest.read_EEP_data_file(data_path, [k])) + # read column pairs from test file + k0 = MESA_data.dtype.names[0] + for k in MESA_data.dtype.names: + if k!=k0: + assert totest.np.array_equal(MESA_data[[k0, k]],\ + totest.read_EEP_data_file(data_path, [k0, k])) + # warning for non readable data + with open(data_path, "w") as test_file: + test_file.write(15*"Test\n") + with warns(match="Problems with reading file "+data_path): + assert totest.read_EEP_data_file(data_path,\ + MESA_data.dtype.names) is None + + def test_fix_He_core(self, MESA_data): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'history'"): + totest.fix_He_core() + # history is None + assert totest.fix_He_core(None) is None + # history is ndarray without the required columns + assert totest.np.array_equal(MESA_data, totest.fix_He_core(MESA_data)) + # history is ndarray with the required columns and corrects them + test_history = totest.np.array([(1, 2, 3, 4), (1, 2, 4, 3),\ + (2, 1, 3, 4), (2, 1, 4, 3)],\ + dtype=[('he_core_mass', ' Date: Mon, 11 Nov 2024 18:59:02 +0100 Subject: [PATCH 29/34] add test_common_functions.py --- .../unit_tests/utils/test_common_functions.py | 2046 +++++++++++++++++ posydon/unit_tests/utils/test_ignorereason.py | 6 +- .../unit_tests/utils/test_posydonwarning.py | 11 + posydon/utils/common_functions.py | 162 +- 4 files changed, 2161 insertions(+), 64 deletions(-) create mode 100644 posydon/unit_tests/utils/test_common_functions.py diff --git a/posydon/unit_tests/utils/test_common_functions.py b/posydon/unit_tests/utils/test_common_functions.py new file mode 100644 index 000000000..313b77244 --- /dev/null +++ b/posydon/unit_tests/utils/test_common_functions.py @@ -0,0 +1,2046 @@ +"""Unit tests of posydon/utils/common_functions.py +""" + +__authors__ = [ + "Matthias Kruckow " +] + +# import the module which will be tested +import posydon.utils.common_functions as totest + +# import other needed code for the tests, which is not already imported in the +# module you like to test +from pytest import fixture, raises, warns, approx +from inspect import isroutine, isclass +from scipy.interpolate import interp1d +from posydon.binary_evol.binarystar import BinaryStar as BinaryStar_class +from posydon.binary_evol.singlestar import SingleStar as SingleStar_class +from posydon.utils.posydonwarning import (EvolutionWarning,\ + InappropriateValueWarning, ApproximationWarning, InterpolationWarning,\ + ReplaceValueWarning, ClassificationWarning) + +@fixture +def BinaryStar(): + # initialize a BinaryStar instance, which is a required argument + return BinaryStar_class() + +@fixture +def SingleStar(): + # initialize a SingleStar instance, which is a required argument + return SingleStar_class() + +@fixture +def star_profile(): + # generate a profile of a test star + profile = totest.np.empty((4,), dtype=[(f, 'f8') for f in ['mass', 'dm',\ + 'radius', 'log_R', 'energy', 'x_mass_fraction_H',\ + 'y_mass_fraction_He', 'z_mass_fraction_metals',\ + 'neutral_fraction_H', 'neutral_fraction_He', 'avg_charge_He']]) + profile['mass'] = totest.np.array([1.0, 0.5, 0.1, 0.001]) + # get dm as differences from mass and the last mass entry (like next would + # be 0) + profile['dm'][:-1] = profile['mass'][:-1] - profile['mass'][1:] + profile['dm'][-1] = profile['mass'][-1] + profile['radius'] = totest.np.array([1.0, 0.5, 0.1, 0.001]) + # get log10 of radius + profile['log_R'] = totest.np.log10(profile['radius']) + profile['energy'] = totest.np.array([1.0, 0.5, 0.1, 0.001])*1.0e+16 + profile['x_mass_fraction_H'] = totest.np.array([0.7, 0.5, 0.1, 0.001]) + profile['y_mass_fraction_He'] = totest.np.array([0.2, 0.5, 0.8, 0.2]) + # get Z=1-X-Y + profile['z_mass_fraction_metals'] = 1.0-profile['x_mass_fraction_H']\ + -profile['y_mass_fraction_He'] + profile['neutral_fraction_H'] = totest.np.array([1.0, 0.5, 0.1, 0.0]) + profile['neutral_fraction_He'] = totest.np.array([1.0, 0.5, 0.1, 0.0]) + profile['avg_charge_He'] = totest.np.array([0.0, 0.5, 1.1, 1.8]) + return profile + +# define test classes collecting several test functions +class TestElements: + # check for objects, which should be an element of the tested module + def test_dir(self): + elements = ['ALL_RLO_CASES', 'ALL_STAR_STATES', 'BURNING_STATES',\ + 'CEE_parameters_from_core_abundance_thresholds',\ + 'COMPACT_OBJECTS', 'CO_radius',\ + 'DEFAULT_CE_OPTION_FOR_LAMBDA', 'He_MS_lifetime',\ + 'LG_MTRANSFER_RATE_THRESHOLD', 'LOG10_BURNING_THRESHOLD',\ + 'MT_CASE_A', 'MT_CASE_B', 'MT_CASE_BA', 'MT_CASE_BB',\ + 'MT_CASE_BC', 'MT_CASE_C', 'MT_CASE_NONBURNING',\ + 'MT_CASE_NO_RLO', 'MT_CASE_TO_STR',\ + 'MT_CASE_UNDETERMINED', 'MT_STR_TO_CASE',\ + 'PATH_TO_POSYDON', 'PchipInterpolator',\ + 'PchipInterpolator2', 'Pwarn',\ + 'REL_LOG10_BURNING_THRESHOLD', 'RICHNESS_STATES',\ + 'RL_RELATIVE_OVERFLOW_THRESHOLD',\ + 'STATE_NS_STARMASS_UPPER_LIMIT', 'STATE_UNDETERMINED',\ + 'Schwarzschild_Radius', 'THRESHOLD_CENTRAL_ABUNDANCE',\ + 'THRESHOLD_HE_NAKED_ABUNDANCE', '__authors__',\ + '__builtins__', '__cached__', '__doc__', '__file__',\ + '__loader__', '__name__', '__package__', '__spec__',\ + 'beaming', 'bondi_hoyle',\ + 'calculate_H2recombination_energy',\ + 'calculate_Mejected_for_integrated_binding_energy',\ + 'calculate_Patton20_values_at_He_depl',\ + 'calculate_binding_energy', 'calculate_core_boundary',\ + 'calculate_lambda_from_profile',\ + 'calculate_recombination_energy', 'check_state_of_star',\ + 'check_state_of_star_history_array', 'const',\ + 'convert_metallicity_to_string', 'copy',\ + 'cumulative_mass_transfer_flag',\ + 'cumulative_mass_transfer_numeric',\ + 'cumulative_mass_transfer_string', 'eddington_limit',\ + 'flip_stars', 'get_binary_state_and_event_and_mt_case',\ + 'get_binary_state_and_event_and_mt_case_array',\ + 'get_i_He_depl', 'get_internal_energy_from_profile',\ + 'get_mass_radius_dm_from_profile', 'histogram_sampler',\ + 'infer_mass_transfer_case', 'infer_star_state',\ + 'initialize_empty_array',\ + 'inspiral_timescale_from_orbital_period',\ + 'inspiral_timescale_from_separation', 'interp1d',\ + 'inverse_sampler', 'is_number',\ + 'linear_interpolation_between_two_cells', 'newton', 'np',\ + 'orbital_period_from_separation',\ + 'orbital_separation_from_period', 'os', 'pd',\ + 'period_change_stabe_MT', 'period_evol_wind_loss',\ + 'profile_recomb_energy', 'quad',\ + 'read_histogram_from_file', 'rejection_sampler',\ + 'roche_lobe_radius', 'rotate', 'rzams',\ + 'separation_evol_wind_loss', 'set_binary_to_failed',\ + 'spin_stable_mass_transfer', 'stefan_boltzmann_law'] + assert dir(totest) == elements, "There might be added or removed "+\ + "objects without an update on the unit test." + + def test_instance_PATH_TO_POSYDON(self): + assert isinstance(totest.PATH_TO_POSYDON, str) + + def test_instance_STATE_UNDETERMINED(self): + assert isinstance(totest.STATE_UNDETERMINED, str) + + def test_instance_BURNING_STATES(self): + assert isinstance(totest.BURNING_STATES, list) + + def test_instance_RICHNESS_STATES(self): + assert isinstance(totest.RICHNESS_STATES, list) + + def test_instance_COMPACT_OBJECTS(self): + assert isinstance(totest.COMPACT_OBJECTS, list) + + def test_instance_ALL_STAR_STATES(self): + assert isinstance(totest.ALL_STAR_STATES, list) + + def test_instance_MT_CASE_NO_RLO(self): + assert isinstance(totest.MT_CASE_NO_RLO, int) + + def test_instance_MT_CASE_A(self): + assert isinstance(totest.MT_CASE_A, int) + + def test_instance_MT_CASE_B(self): + assert isinstance(totest.MT_CASE_B, int) + + def test_instance_MT_CASE_C(self): + assert isinstance(totest.MT_CASE_C, int) + + def test_instance_MT_CASE_BA(self): + assert isinstance(totest.MT_CASE_BA, int) + + def test_instance_MT_CASE_BB(self): + assert isinstance(totest.MT_CASE_BB, int) + + def test_instance_MT_CASE_BC(self): + assert isinstance(totest.MT_CASE_BC, int) + + def test_instance_MT_CASE_NONBURNING(self): + assert isinstance(totest.MT_CASE_NONBURNING, int) + + def test_instance_MT_CASE_UNDETERMINED(self): + assert isinstance(totest.MT_CASE_UNDETERMINED, int) + + def test_instance_ALL_RLO_CASES(self): + assert isinstance(totest.ALL_RLO_CASES, set) + + def test_instance_MT_CASE_TO_STR(self): + assert isinstance(totest.MT_CASE_TO_STR, dict) + + def test_instance_MT_STR_TO_CASE(self): + assert isinstance(totest.MT_STR_TO_CASE, dict) + + def test_instance_DEFAULT_CE_OPTION_FOR_LAMBDA(self): + assert isinstance(totest.DEFAULT_CE_OPTION_FOR_LAMBDA, str) + + def test_instance_is_number(self): + assert isroutine(totest.is_number) + + def test_instance_stefan_boltzmann_law(self): + assert isroutine(totest.stefan_boltzmann_law) + + def test_instance_rzams(self): + assert isroutine(totest.rzams) + + def test_instance_roche_lobe_radius(self): + assert isroutine(totest.roche_lobe_radius) + + def test_instance_orbital_separation_from_period(self): + assert isroutine(totest.orbital_separation_from_period) + + def test_instance_orbital_period_from_separation(self): + assert isroutine(totest.orbital_period_from_separation) + + def test_instance_eddington_limit(self): + assert isroutine(totest.eddington_limit) + + def test_instance_beaming(self): + assert isroutine(totest.beaming) + + def test_instance_bondi_hoyle(self): + assert isroutine(totest.bondi_hoyle) + + def test_instance_rejection_sampler(self): + assert isroutine(totest.rejection_sampler) + + def test_instance_inverse_sampler(self): + assert isroutine(totest.inverse_sampler) + + def test_instance_histogram_sampler(self): + assert isroutine(totest.histogram_sampler) + + def test_instance_read_histogram_from_file(self): + assert isroutine(totest.read_histogram_from_file) + + def test_instance_inspiral_timescale_from_separation(self): + assert isroutine(totest.inspiral_timescale_from_separation) + + def test_instance_inspiral_timescale_from_orbital_period(self): + assert isroutine(totest.inspiral_timescale_from_orbital_period) + + def test_instance_spin_stable_mass_transfer(self): + assert isroutine(totest.spin_stable_mass_transfer) + + def test_instance_check_state_of_star(self): + assert isroutine(totest.check_state_of_star) + + def test_instance_check_state_of_star_history_array(self): + assert isroutine(totest.check_state_of_star_history_array) + + def test_instance_get_binary_state_and_event_and_mt_case(self): + assert isroutine(totest.get_binary_state_and_event_and_mt_case) + + def test_instance_get_binary_state_and_event_and_mt_case_array(self): + assert isroutine(totest.get_binary_state_and_event_and_mt_case_array) + + def test_instance_CO_radius(self): + assert isroutine(totest.CO_radius) + + def test_instance_He_MS_lifetime(self): + assert isroutine(totest.He_MS_lifetime) + + def test_instance_Schwarzschild_Radius(self): + assert isroutine(totest.Schwarzschild_Radius) + + def test_instance_flip_stars(self): + assert isroutine(totest.flip_stars) + + def test_instance_set_binary_to_failed(self): + assert isroutine(totest.set_binary_to_failed) + + def test_instance_infer_star_state(self): + assert isroutine(totest.infer_star_state) + + def test_instance_infer_mass_transfer_case(self): + assert isroutine(totest.infer_mass_transfer_case) + + def test_instance_cumulative_mass_transfer_numeric(self): + assert isroutine(totest.cumulative_mass_transfer_numeric) + + def test_instance_cumulative_mass_transfer_string(self): + assert isroutine(totest.cumulative_mass_transfer_string) + + def test_instance_cumulative_mass_transfer_flag(self): + assert isroutine(totest.cumulative_mass_transfer_flag) + + def test_instance_get_i_He_depl(self): + assert isroutine(totest.get_i_He_depl) + + def test_instance_calculate_Patton20_values_at_He_depl(self): + assert isroutine(totest.calculate_Patton20_values_at_He_depl) + + def test_instance_CEE_parameters_from_core_abundance_thresholds(self): + assert isroutine(totest.CEE_parameters_from_core_abundance_thresholds) + + def test_instance_initialize_empty_array(self): + assert isroutine(totest.initialize_empty_array) + + def test_instance_calculate_core_boundary(self): + assert isroutine(totest.calculate_core_boundary) + + def test_instance_period_evol_wind_loss(self): + assert isroutine(totest.period_evol_wind_loss) + + def test_instance_separation_evol_wind_loss(self): + assert isroutine(totest.separation_evol_wind_loss) + + def test_instance_period_change_stabe_MT(self): + assert isroutine(totest.period_change_stabe_MT) + + def test_instance_linear_interpolation_between_two_cells(self): + assert isroutine(totest.linear_interpolation_between_two_cells) + + def test_instance_calculate_lambda_from_profile(self): + assert isroutine(totest.calculate_lambda_from_profile) + + def test_instance_get_mass_radius_dm_from_profile(self): + assert isroutine(totest.get_mass_radius_dm_from_profile) + + def test_instance_get_internal_energy_from_profile(self): + assert isroutine(totest.get_internal_energy_from_profile) + + def test_instance_calculate_H2recombination_energy(self): + assert isroutine(totest.calculate_H2recombination_energy) + + def test_instance_calculate_recombination_energy(self): + assert isroutine(totest.calculate_recombination_energy) + + def test_instance_profile_recomb_energy(self): + assert isroutine(totest.profile_recomb_energy) + + def test_instance_calculate_binding_energy(self): + assert isroutine(totest.calculate_binding_energy) + + def test_instance_calculate_Mejected_for_integrated_binding_energy(self): + assert isroutine(totest.\ + calculate_Mejected_for_integrated_binding_energy) + + def test_instance_PchipInterpolator2(self): + assert isclass(totest.PchipInterpolator2) + + def test_instance_convert_metallicity_to_string(self): + assert isroutine(totest.convert_metallicity_to_string) + + def test_instance_rotate(self): + assert isroutine(totest.rotate) + + +class TestValues: + # check that the values fit + def test_value_PATH_TO_POSYDON(self): + assert '/' in totest.PATH_TO_POSYDON + + def test_value_STATE_UNDETERMINED(self): + assert totest.STATE_UNDETERMINED == "undetermined_evolutionary_state" + + def test_value_BURNING_STATES(self): + for v in ["Core_H_burning", "Core_He_burning", "Shell_H_burning",\ + "Central_He_depleted", "Central_C_depletion"]: + # check required values + assert v in totest.BURNING_STATES, "missing entry" + + def test_value_RICHNESS_STATES(self): + for v in ["H-rich", "stripped_He"]: #TODO: check add "accreted_He" + # check required values + assert v in totest.RICHNESS_STATES, "missing entry" + + def test_value_COMPACT_OBJECTS(self): + for v in ["WD", "NS", "BH","massless_remnant"]: + # check required values + assert v in totest.COMPACT_OBJECTS, "missing entry" + + def test_value_ALL_STAR_STATES(self): + for v in totest.COMPACT_OBJECTS: + # check required values of COMPACT_OBJECTS + assert v in totest.ALL_STAR_STATES, "missing entry" + # check required STATE_UNDETERMINED + assert totest.STATE_UNDETERMINED in totest.ALL_STAR_STATES,\ + "missing entry" + for v1 in totest.RICHNESS_STATES: + for v2 in totest.BURNING_STATES: + # check required values of combinatios of RICHNESS_STATES and + # BURNING_STATES + v = v1+"_"+v2 + assert v in totest.ALL_STAR_STATES, "missing entry" + + def test_value_MT_CASE_NO_RLO(self): + assert totest.MT_CASE_NO_RLO == 0 + + def test_value_MT_CASE_A(self): + assert totest.MT_CASE_A == 1 + + def test_value_MT_CASE_B(self): + assert totest.MT_CASE_B == 2 + + def test_value_MT_CASE_C(self): + assert totest.MT_CASE_C == 3 + + def test_value_MT_CASE_BA(self): + assert totest.MT_CASE_BA == 4 + + def test_value_MT_CASE_BB(self): + assert totest.MT_CASE_BB == 5 + + def test_value_MT_CASE_BC(self): + assert totest.MT_CASE_BC == 6 + + def test_value_MT_CASE_NONBURNING(self): + assert totest.MT_CASE_NONBURNING == 8 + + def test_value_MT_CASE_UNDETERMINED(self): + assert totest.MT_CASE_UNDETERMINED == 9 + + def test_value_ALL_RLO_CASES(self): + for v in [totest.MT_CASE_A, totest.MT_CASE_B, totest.MT_CASE_C,\ + totest.MT_CASE_BA, totest.MT_CASE_BB, totest.MT_CASE_BC,\ + totest.MT_CASE_NONBURNING]: + # check required values + assert v in totest.ALL_RLO_CASES, "missing entry" + + def test_value_MT_CASE_TO_STR(self): + for v in [totest.MT_CASE_NO_RLO, totest.MT_CASE_A, totest.MT_CASE_B,\ + totest.MT_CASE_C, totest.MT_CASE_BA, totest.MT_CASE_BB,\ + totest.MT_CASE_BC, totest.MT_CASE_NONBURNING,\ + totest.MT_CASE_UNDETERMINED]: + # check required values + assert v in totest.MT_CASE_TO_STR.keys(), "missing entry" + + def test_value_MT_STR_TO_CASE(self): + for k,v in totest.MT_STR_TO_CASE.items(): + # check required items + assert v in totest.MT_CASE_TO_STR.keys() + + def test_value_DEFAULT_CE_OPTION_FOR_LAMBDA(self): + assert totest.DEFAULT_CE_OPTION_FOR_LAMBDA == "lambda_from_profile_"+\ + "gravitational_plus_internal_minus_recombination" + + +class TestFunctions: + @fixture + def csv_path(self, tmp_path): + # a temporary csv path for testing + return totest.os.path.join(tmp_path, "hist.csv") + + @fixture + def csv_file(self, csv_path): + # a temporary csv file for testing + with open(csv_path, "w") as test_file: + test_file.write("0.0,1.0\n") + test_file.write("1.0,1.0\n\n") + test_file.write("2.0,1.0\n") + return + + @fixture + def csv_path2(self, tmp_path): + # a temporary csv path for testing + return totest.os.path.join(tmp_path, "hist2.csv") + + @fixture + def csv_file2(self, csv_path2): + # a temporary csv file for testing + with open(csv_path2, "w") as test_file: + test_file.write("#0.0,1.0\n") + test_file.write("0.0,1.0,2.0\n") + test_file.write("1.0,1.0,1.0\n") + return + + # test functions + def test_is_number(self): + # missing argument + with raises(TypeError, match="missing 1 required positional argument"): + totest.is_number() + # try a string of a float + assert totest.is_number("1.2e-3") + # try a non-float string + assert totest.is_number("test")==False + + def test_stefan_boltzmann_law(self): + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'L' and 'R'"): + totest.stefan_boltzmann_law() + # examples + assert totest.stefan_boltzmann_law(1.0, 1.0) ==\ + approx(5.77603658298e+3, abs=6e-9) + assert totest.stefan_boltzmann_law(2.0, 1.0) ==\ + approx(6.86890380099e+3, abs=6e-9) + assert totest.stefan_boltzmann_law(1.0, 2.0) ==\ + approx(4.08427463621e+3, abs=6e-9) + + def test_rzams(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'm'"): + totest.rzams() + # examples + assert totest.rzams(1.0) ==\ + approx(0.88824945030, abs=6e-12) + assert totest.rzams(2.0) ==\ + approx(1.61021038543, abs=6e-12) + assert totest.rzams(1.0, z=1.0) ==\ + approx(0.85822941705, abs=6e-12) + assert totest.rzams(1.0, Zsun=1.0) ==\ + approx(0.84963691291, abs=6e-12) + + def test_roche_lobe_radius(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'q'"): + totest.roche_lobe_radius() + # examples + assert totest.roche_lobe_radius(1.0) ==\ + approx(0.37892051838, abs=6e-12) + assert totest.roche_lobe_radius(2.0) ==\ + approx(0.44000423753, abs=6e-12) + assert totest.roche_lobe_radius(1.0, a_orb=2.0) ==\ + approx(0.75784103676, abs=6e-12) + # check that roche lobe sum never exceeds orbital separation + for q in [1.0e+1, 1.0e+2, 1.0e+3, 1.0e+4, 1.0e+5, 1.0e+6, 1.0e+7]: + assert totest.roche_lobe_radius(q)+totest.roche_lobe_radius(1.0/q)\ + < 1.0 + + def test_orbital_separation_from_period(self): + # missing argument + with raises(TypeError, match="missing 3 required positional"+\ + " arguments: 'period_days', 'm1_solar', and 'm2_solar'"): + totest.orbital_separation_from_period() + # examples + assert totest.orbital_separation_from_period(1.0, 15.0, 30.0) ==\ + approx(14.9643417735, abs=6e-11) + assert totest.orbital_separation_from_period(2.0, 15.0, 30.0) ==\ + approx(23.7544118733, abs=6e-11) + assert totest.orbital_separation_from_period(1.0, 30.0, 30.0) ==\ + approx(16.4703892879, abs=6e-11) + assert totest.orbital_separation_from_period(1.0, 15.0, 60.0) ==\ + approx(17.7421890201, abs=6e-11) + + def test_orbital_period_from_separation(self): + # missing argument + with raises(TypeError, match="missing 3 required positional"+\ + " arguments: 'separation', 'm1', and 'm2'"): + totest.orbital_period_from_separation() + # examples + assert totest.orbital_period_from_separation(1.0, 15.0, 30.0) ==\ + approx(1.72773763511e-2, abs=6e-14) + assert totest.orbital_period_from_separation(2.0, 15.0, 30.0) ==\ + approx(4.88677999159e-2, abs=6e-14) + assert totest.orbital_period_from_separation(1.0, 30.0, 30.0) ==\ + approx(1.49626468308e-2, abs=6e-14) + assert totest.orbital_period_from_separation(1.0, 15.0, 60.0) ==\ + approx(1.33829981748e-2, abs=6e-14) + + def test_eddington_limit(self, BinaryStar): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'binary'"): + totest.eddington_limit() + # bad input + with raises(ValueError, match="Eddington limit is being calculated"+\ + " for a non-CO"): + totest.eddington_limit(BinaryStar) + with raises(IndexError, match="index 2 is out of bounds"): + BinaryStar.star_1.state = 'WD' + totest.eddington_limit(BinaryStar, idx=2) + # examples: 1Msun accretor is star1 + BinaryStar.star_1.mass = 1.0 + for CO, r in zip(['WD', 'NS', 'BH'],\ + [(approx(4.01681147088e-17, abs=6e-29),\ + approx(6.40623627946e+7, abs=6e-5)),\ + (approx(2.17747384546e-8, abs=6e-20),\ + approx(0.11817658993, abs=6e-12)),\ + (approx(4.49942509871e-8, abs=6e-20),\ + approx(5.71909584179e-2, abs=6e-14))]): + BinaryStar.star_1.state = CO + assert totest.eddington_limit(BinaryStar) == r + BinaryStar.star_1.state = None + # examples: 1.2Msun accretor is star2, while donor has X_surf=0.1 + BinaryStar.star_2.mass = 1.2 + for CO, r in zip(['WD', 'NS', 'BH'],\ + [(approx(5.89502616630e-17, abs=6e-29),\ + approx(8.16917025429e+7, abs=6e-5)),\ + (approx(3.39586943808e-8, abs=6e-20),\ + approx(0.14181190792, abs=6e-12)),\ + (approx(8.42046955292e-8, abs=6e-20),\ + approx(5.71909584179e-2, abs=6e-14))]): + BinaryStar.star_2.state = CO + BinaryStar.star_1.surface_h1 = 0.1 + assert totest.eddington_limit(BinaryStar) == r + # examples: 1.3Msun accretor is star1 with history + BinaryStar.star_1.state = 'BH' + BinaryStar.star_1.mass_history = totest.np.array([1.3, 1.3]) + BinaryStar.star_1.state_history = totest.np.array([None, None]) + with raises(ValueError, match='COtype must be "BH", "NS", or "WD"'): + totest.eddington_limit(BinaryStar, idx=0) + for CO, r in zip(['WD', 'NS', 'BH'],\ + [(approx(3.68044499210e-17, abs=6e-29),\ + approx(9.08923688740e+7, abs=6e-5)),\ + (approx(2.17747384546e-8, abs=6e-20),\ + approx(0.15362956691, abs=6e-12)),\ + (approx(5.84925262833e-8, abs=6e-20),\ + approx(5.71909584179e-2, abs=6e-14))]): + BinaryStar.star_1.state_history = totest.np.array([None, CO]) + assert totest.eddington_limit(BinaryStar, idx=0) == r + + def test_beaming(self, BinaryStar): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'binary'"): + totest.beaming() + # bad input + with raises(ValueError, match="Eddington limit is being calculated"+\ + " for a non-CO"): + totest.beaming(BinaryStar) + # examples: 1Msun accretor is star1 with no mass-transfer rate + BinaryStar.star_1.mass = 1.0 + for CO, r in zip(['WD', 'NS', 'BH'],\ + [(totest.np.nan, 1),\ + (totest.np.nan, 1),\ + (totest.np.nan, 1)]): + BinaryStar.star_1.state = CO + assert totest.beaming(BinaryStar) == r + # examples: 1.2Msun accretor is star1 + BinaryStar.star_1.mass = 1.2 + BinaryStar.lg_mtransfer_rate = -7.5 + for CO, r in zip(['WD', 'NS', 'BH'],\ + [(approx(7.80787388850, abs=6e-12),\ + approx(1.04303350643e-16, abs=6e-28)),\ + (approx(2.98994840792e-8, abs=6e-20), 1),\ + (approx(3.16227766017e-8, abs=6e-20), 1)]): + BinaryStar.star_1.state = CO + assert totest.beaming(BinaryStar) == r + + def test_bondi_hoyle(self, BinaryStar, monkeypatch): + def mock_rand(shape): + return totest.np.zeros(shape) + # missing argument + with raises(TypeError, match="missing 3 required positional"+\ + " arguments: 'binary', 'accretor', and 'donor'"): + totest.bondi_hoyle() + # bad input + with raises(RuntimeError, match="Failed to converge after 100"+\ + " iterations"): + totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2) + # examples: + BinaryStar.separation = 1.0 #a semi-major axis of 1Rsun + BinaryStar.eccentricity = 0.1 #a small eccentricity + BinaryStar.star_1.mass = 1.1 #accretor's mass is 1.1Msun + BinaryStar.star_1.state = 'WD' #accretor is WD + BinaryStar.star_2.mass = 1.2 #donor's mass is 1.2Msun + BinaryStar.star_2.lg_wind_mdot = -10.0 #donor's wind is 10^{-10}Msun/yr + BinaryStar.star_2.he_core_radius = 0.2 #donor's he-core rad. is 0.2Rsun + BinaryStar.star_2.log_R = -0.5 #donor's radius is 10^{-0.5}Rsun + BinaryStar.star_2.surface_h1 = 0.7 #donor's X_surf=0.7 + BinaryStar.star_2.log_L = 0.3 #donor's lum. is 10^{0.3}Lsun + with raises(UnboundLocalError, match="cannot access local variable"+\ + " 'f_m' where it is not associated with a value"): + # undefined scheme + totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2, scheme='') + monkeypatch.setattr(totest.np.random, "rand", mock_rand) + assert totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2) == approx(3.92668160462e-17, abs=6e-29) + assert totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2, scheme='Kudritzki+2000') ==\ + approx(3.92668160462e-17, abs=6e-29) + BinaryStar.star_2.log_R = 1.5 #donor's radius is 10^{1.5}Rsun + assert totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2, scheme='Kudritzki+2000') ==\ + approx(3.92668160462e-17, abs=6e-29) + BinaryStar.star_2.log_R = -1.5 #donor's radius is 10^{-1.5}Rsun + assert totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2, scheme='Kudritzki+2000') == 1e-99 + BinaryStar.star_2.surface_h1 = 0.25 #donor's X_surf=0.25 + assert totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2) == 1e-99 + BinaryStar.star_2.lg_wind_mdot = -4.0 #donor's wind is 10^{-4}Msun/yr + assert totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2) == 1e-99 + assert totest.bondi_hoyle(BinaryStar, BinaryStar.star_1,\ + BinaryStar.star_2, wind_disk_criteria=False) ==\ + approx(5.34028698228e-17, abs=6e-29) # form always a disk + + def test_rejection_sampler(self, monkeypatch): + def mock_uniform(low=0.0, high=1.0, size=1): + return totest.np.linspace(low, high, num=size) + def mock_interp1d(x, y): + if x[0]>x[-1]: + raise ValueError + return interp1d(x, y) + def mock_pdf(x): + return 1.0-totest.np.sqrt(x) + # bad input + with raises(TypeError, match="'>=' not supported between instances"+\ + " of 'NoneType' and 'float'"): + totest.rejection_sampler() + # examples: + monkeypatch.setattr(totest.np.random, "uniform", mock_uniform) + monkeypatch.setattr(totest, "interp1d", mock_interp1d) + assert totest.np.array_equal(totest.rejection_sampler(\ + x=totest.np.array([0.0, 1.0]), y=totest.np.array([0.4, 0.6]),\ + size=5), totest.np.array([0.0, 0.25, 0.5, 0.75, 1.0])) + assert totest.np.array_equal(totest.rejection_sampler(\ + x=totest.np.array([1.0, 0.0]), y=totest.np.array([0.2, 0.8]),\ + size=5), totest.np.array([0.0, 0.25, 0.5, 0.0, 0.0])) + assert totest.np.array_equal(totest.rejection_sampler(\ + x_lim=totest.np.array([0.0, 1.0]), pdf=mock_pdf,\ + size=5), totest.np.array([0.0, 0.25, 0.0, 0.0, 0.0])) + + def test_inverse_sampler(self, monkeypatch): + def mock_uniform(low=0.0, high=1.0, size=1): + return totest.np.linspace(low, high, num=size) + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'x' and 'y'"): + totest.inverse_sampler() + # bad input + with raises(ValueError, match="diff requires input that is at least"+\ + " one dimensional"): + totest.inverse_sampler(x=None, y=None) + # examples: + monkeypatch.setattr(totest.np.random, "uniform", mock_uniform) + assert totest.np.allclose(totest.inverse_sampler(\ + x=totest.np.array([0.0, 1.0]), y=totest.np.array([0.4, 0.6]),\ + size=5), totest.np.array([0.0, 0.29128785, 0.54950976,\ + 0.78388218, 1.0])) + with warns(RuntimeWarning,\ + match="invalid value encountered in divide"): + assert totest.np.allclose(totest.inverse_sampler(\ + x=totest.np.array([0.0, 1.0]),\ + y=totest.np.array([0.5, 0.5]), size=5),\ + totest.np.array([0.0, 0.25, 0.5, 0.75, 1.0])) + + def test_histogram_sampler(self, monkeypatch): + def mock_uniform(low=0.0, high=1.0, size=1): + return totest.np.linspace(low, high, num=size) + def mock_choice(a, size=None, replace=True, p=None): + if isinstance(a, int): + a=totest.np.arange(a) + sample = [] + for v,q in zip(a,p): + sample += round(size*q) * [v] + if len(sample)0: + return totest.MT_CASE_A + else: + return totest.MT_CASE_UNDETERMINED + else: + return totest.MT_CASE_NO_RLO + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'binary'"): + totest.get_binary_state_and_event_and_mt_case() + # bad input + with raises(TypeError, match="argument of type 'NoneType' is not"+\ + " iterable"): + totest.get_binary_state_and_event_and_mt_case(BinaryStar) + # examples: no binary + assert totest.get_binary_state_and_event_and_mt_case(None) ==\ + [None, None, 'None'] + # examples: not converged + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar,\ + interpolation_class='not_converged') == [None, None, 'None'] + # examples: initial MT + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar,\ + interpolation_class='initial_MT') ==\ + ['initial_RLOF', None, 'None'] + # examples: detached + BinaryStar.star_1.state = "test_state" + BinaryStar.star_2.state = "test_state" + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar) ==\ + ['detached', None, 'None'] + BinaryStar.star_1.state_history[0] = "test_state" + BinaryStar.star_2.state_history[0] = "test_state" + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar, i=0)\ + == ['detached', None, 'None'] + # examples: mass transfer + monkeypatch.setattr(totest, "infer_mass_transfer_case",\ + mock_infer_mass_transfer_case) + ## donor is star 1 + BinaryStar.rl_relative_overflow_1 = 1.0 + for IC,e in zip([None, 'unstable_MT'], [None, 'oCE1']): + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar,\ + interpolation_class=IC) == ['RLO1', e, 'case_A1'] + ## both stars overfill RL leading to double CE initiated by star 1 + BinaryStar.rl_relative_overflow_2 = 1.0 + for IC,e in zip([None, 'unstable_MT'], [None, 'oDoubleCE1']): + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar,\ + interpolation_class=IC) == ['contact', e, 'None'] + ## classical CE initiated by star 1 + BinaryStar.star_2.state = "BH" + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar,\ + interpolation_class='unstable_MT') == ['contact', 'oCE1', 'None'] + BinaryStar.star_2.state = "test_state" + ## both stars overfill RL leading to double CE initiated by star 2 + BinaryStar.rl_relative_overflow_2 = 2.0 + for IC,e in zip([None, 'unstable_MT'], [None, 'oDoubleCE2']): + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar,\ + interpolation_class=IC) == ['contact', e, 'None'] + ## classical CE initiated by star 2 + BinaryStar.star_1.state = "BH" + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar,\ + interpolation_class='unstable_MT') == ['contact', 'oCE2', 'None'] + BinaryStar.star_1.state = "test_state" + ## donor is star 2 + BinaryStar.rl_relative_overflow_1 = None + for IC,e in zip([None, 'unstable_MT'], [None, 'oCE2']): + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar,\ + interpolation_class=IC) == ['RLO2', e, 'case_A2'] + # examples: undefined + BinaryStar.rl_relative_overflow_2 = -1.0 + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar) ==\ + ['undefined', None, 'None'] + # examples: star 2 becomes WD + BinaryStar.star_2.center_gamma = 10.0 + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar) ==\ + ['undefined', 'CC2', 'None'] + # examples: star 1 becomes WD + BinaryStar.star_1.center_gamma = 10.0 + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar) ==\ + ['undefined', 'CC1', 'None'] + # examples: no center_gamma_history + BinaryStar.star_1.center_gamma_history=[] + BinaryStar.star_2.center_gamma_history=[] + assert totest.get_binary_state_and_event_and_mt_case(BinaryStar, i=0)\ + == ['detached', None, 'None'] + + def test_get_binary_state_and_event_and_mt_case_array(self, BinaryStar,\ + monkeypatch): + def mock_get_binary_state_and_event_and_mt_case(binary,\ + interpolation_class=None, i=None, verbose=False): + return ['Unit', 'Test', 'None'] + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'binary'"): + totest.get_binary_state_and_event_and_mt_case_array() + # bad input + with raises(TypeError, match="argument of type 'NoneType' is not"+\ + " iterable"): + totest.get_binary_state_and_event_and_mt_case_array(BinaryStar) + with raises(IndexError, match="list index out of range"): + totest.get_binary_state_and_event_and_mt_case_array(BinaryStar,\ + N=10) + # examples: no binary + assert totest.get_binary_state_and_event_and_mt_case_array(None) ==\ + (None, None, 'None') + # examples: detached + BinaryStar.star_1.state = "test_state" + BinaryStar.star_2.state = "test_state" + assert totest.get_binary_state_and_event_and_mt_case_array(BinaryStar)\ + == ('detached', None, 'None') + # examples: several mocked entries + monkeypatch.setattr(totest, "get_binary_state_and_event_and_mt_case",\ + mock_get_binary_state_and_event_and_mt_case) + mock_return = mock_get_binary_state_and_event_and_mt_case(BinaryStar) + for i in range(3): + assert totest.get_binary_state_and_event_and_mt_case_array(\ + BinaryStar, N=i+1) == ((i+1)*[mock_return[0]],\ + (i+1)*[mock_return[1]], (i+1)*[mock_return[2]]) + + def test_CO_radius(self): + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'M' and 'COtype'"): + totest.CO_radius() + # bad input + with raises(ValueError, match="Compact object mass must be a"+\ + " positive value"): + totest.CO_radius(0.0, 'BH') + with raises(ValueError, match="COtype not in the list of valid"+\ + " options"): + totest.CO_radius(1.0, 'TEST') + # examples: + for CO,m,r in zip(['WD', 'WD', 'NS', 'NS', 'BH', 'BH'],\ + [0.5, 1.0, 1.5, 2.0, 7.0, 9.0],\ + [approx(5.24982189818e-3, abs=6e-15),\ + approx(4.16678640191e-3, abs=6e-15),\ + approx(1.79602862151e-5, abs=6e-17),\ + approx(1.79602862151e-5, abs=6e-17),\ + totest.Schwarzschild_Radius(7.0),\ + totest.Schwarzschild_Radius(9.0)]): + assert totest.CO_radius(m, CO) == r + + def test_He_MS_lifetime(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'mass'"): + totest.He_MS_lifetime() + # bad input + with raises(TypeError, match="'<' not supported between instances"+\ + " of 'NoneType' and 'float'"): + totest.He_MS_lifetime(None) + # examples: + for m,t in zip([1.0, 3.0, 30.0, 300.0], [1.0e+8,\ + approx(3.47136114375e+7, abs=6e-5),\ + approx(6.95883527992e+5, abs=6e-7), 3.0e+5]): + assert totest.He_MS_lifetime(m) == t + + def test_Schwarzschild_Radius(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'M'"): + totest.Schwarzschild_Radius() + # bad input + with raises(TypeError) as error_info: + totest.Schwarzschild_Radius(None) + assert error_info.value.args[0] == "unsupported operand type(s) for"+\ + " *: 'float' and 'NoneType'" + # examples: + for m,r in zip([7.0, 70.0], [\ + approx(2.97147953078e-5, abs=6e-17),\ + approx(2.97147953078e-4, abs=6e-16)]): + assert totest.Schwarzschild_Radius(m) == r + + def test_flip_stars(self, BinaryStar): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'binary'"): + totest.flip_stars() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'star_1'"): + totest.flip_stars(None) + # examples: set stars and check swap + for attr in BinaryStar.star_1.__dict__: + setattr(BinaryStar.star_1, attr, 1) + for attr in BinaryStar.star_2.__dict__: + setattr(BinaryStar.star_2, attr, 2) + totest.flip_stars(BinaryStar) + for attr in BinaryStar.star_1.__dict__: + assert getattr(BinaryStar.star_1, attr) == 2 + for attr in BinaryStar.star_2.__dict__: + assert getattr(BinaryStar.star_2, attr) == 1 + # examples: binary states + for s,n in zip(['RLO1', 'RLO2'], ['RLO2', 'RLO1']): + BinaryStar.state = s + BinaryStar.state_history[0] = s + totest.flip_stars(BinaryStar) + assert BinaryStar.state == n + assert BinaryStar.state_history[0] == n + # examples: binary events + for e,n in zip(['oRLO1', 'oRLO2', 'oCE1', 'oCE2', 'CC1', 'CC2'],\ + ['oRLO2', 'oRLO1', 'oCE2', 'oCE1', 'CC2', 'CC1']): + BinaryStar.event = e + BinaryStar.event_history[0] = e + totest.flip_stars(BinaryStar) + assert BinaryStar.event == n + assert BinaryStar.event_history[0] == n + # examples: set binary values and check swap + for v in BinaryStar.__dict__: + if v[-1]==1: + setattr(BinaryStar, v, 1) + elif v[-1]==2: + setattr(BinaryStar, v, 2) + totest.flip_stars(BinaryStar) + for v in BinaryStar.__dict__: + if v[-1]==1: + assert getattr(BinaryStar, v) == 2 + elif v[-1]==2: + assert getattr(BinaryStar, v) == 1 + + def test_set_binary_to_failed(self, BinaryStar): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'binary'"): + totest.set_binary_to_failed() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'state'"): + totest.set_binary_to_failed(None) + # examples: + totest.set_binary_to_failed(BinaryStar) + assert BinaryStar.state == "ERR" + assert BinaryStar.event == "FAILED" + + def test_infer_star_state(self): + # bad input + with raises(TypeError, match="'<=' not supported between instances"+\ + " of 'NoneType' and 'float'"): + totest.infer_star_state(star_CO=True) + # examples: undetermined + assert totest.infer_star_state() == totest.STATE_UNDETERMINED + # examples: compact objects + for m,CO in zip([0.5*totest.STATE_NS_STARMASS_UPPER_LIMIT,\ + totest.STATE_NS_STARMASS_UPPER_LIMIT,\ + 2.0*totest.STATE_NS_STARMASS_UPPER_LIMIT],\ + ["NS", "NS", "BH"]): + assert totest.infer_star_state(star_mass=m, star_CO=True) == CO + # examples: loop over all cases + THNA = totest.THRESHOLD_HE_NAKED_ABUNDANCE + TCA = totest.THRESHOLD_CENTRAL_ABUNDANCE + LBT = totest.LOG10_BURNING_THRESHOLD + for sH1 in [2.0*THNA, THNA, 0.5*THNA]: + for cH1 in [2.0*TCA, TCA, 0.5*TCA]: + for cHe4 in [2.0*TCA, TCA, 0.5*TCA]: + for cC12 in [2.0*TCA, TCA, 0.5*TCA]: + for lgLH in [2.0*LBT, LBT, 0.5*LBT]: + for lgLHe in [2.0*LBT, LBT, 0.5*LBT]: + if sH1>THNA: + rich = "H-rich" + else: + rich = "stripped_He" + if cH1<=TCA and cHe4<=TCA and cC12<=TCA: + burn = "Central_C_depletion" + elif cH1<=TCA and cHe4<=TCA and cC12>TCA: + burn = "Central_He_depleted" + elif cH1>TCA and lgLH>LBT: + burn = "Core_H_burning" + elif cH1>TCA and lgLH<=LBT: + burn = "non_burning" + elif lgLHe>LBT: + burn = "Core_He_burning" + elif lgLH>LBT: + burn = "Shell_H_burning" + else: + burn = "non_burning" + assert totest.infer_star_state(surface_h1=sH1,\ + center_h1=cH1, center_he4=cHe4,\ + center_c12=cC12, log_LH=lgLH,\ + log_LHe=lgLHe, log_Lnuc=lgLH+lgLHe) ==\ + rich+"_"+burn + + def test_infer_mass_transfer_case(self, capsys): + # missing argument + with raises(TypeError, match="missing 3 required positional"+\ + " arguments: 'rl_relative_overflow',"+\ + " 'lg_mtransfer_rate', and 'donor_state'"): + totest.infer_mass_transfer_case() + # examples: no RLO + assert totest.infer_mass_transfer_case(None, None, None) ==\ + totest.MT_CASE_NO_RLO + assert totest.infer_mass_transfer_case(None, 1.0, "test") ==\ + totest.MT_CASE_NO_RLO + assert totest.infer_mass_transfer_case(1.0, None, "test") ==\ + totest.MT_CASE_NO_RLO + RROT = totest.RL_RELATIVE_OVERFLOW_THRESHOLD + LMRT = totest.LG_MTRANSFER_RATE_THRESHOLD + assert totest.infer_mass_transfer_case(min(-1.0, RROT), LMRT, "test")\ + == totest.MT_CASE_NO_RLO + assert totest.infer_mass_transfer_case(min(-1.0, RROT), LMRT, "test",\ + verbose=True) == totest.MT_CASE_NO_RLO + assert "checking rl_relative_overflow / lg_mtransfer_rate" in\ + capsys.readouterr().out + # examples: MT cases + for ds,c in zip(["test_non_burning", "H-rich_Core_H_burning",\ + "H-rich_Core_He_burning", "H-rich_Shell_H_burning",\ + "H-rich_Central_He_depleted",\ + "H-rich_Central_C_depletion", "H-rich_undetermined",\ + "stripped_He_Core_He_burning",\ + "stripped_He_Central_He_depleted",\ + "stripped_He_Central_C_depletion",\ + "stripped_He_undetermined", "test_undetermined"],\ + [totest.MT_CASE_NONBURNING, totest.MT_CASE_A,\ + totest.MT_CASE_B, totest.MT_CASE_B, totest.MT_CASE_C,\ + totest.MT_CASE_C, totest.MT_CASE_UNDETERMINED,\ + totest.MT_CASE_BA, totest.MT_CASE_BB,\ + totest.MT_CASE_BB, totest.MT_CASE_UNDETERMINED,\ + totest.MT_CASE_UNDETERMINED]): + assert totest.infer_mass_transfer_case(2*RROT, 2*LMRT, ds) == c + + def test_cumulative_mass_transfer_numeric(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'MT_cases'"): + totest.cumulative_mass_transfer_numeric() + # bad input + with raises(TypeError, match="object of type 'NoneType' has no len()"): + totest.cumulative_mass_transfer_numeric(None) + # examples: no cases + assert totest.cumulative_mass_transfer_numeric([]) ==\ + [totest.MT_CASE_UNDETERMINED] + # examples: undetermined + assert totest.cumulative_mass_transfer_numeric(\ + [totest.MT_CASE_UNDETERMINED]) == [totest.MT_CASE_UNDETERMINED] + # examples: no RLO + assert totest.cumulative_mass_transfer_numeric(\ + [totest.MT_CASE_NO_RLO]) == [totest.MT_CASE_NO_RLO] + # examples: cut out undetermined + assert totest.cumulative_mass_transfer_numeric([totest.MT_CASE_A,\ + totest.MT_CASE_UNDETERMINED, totest.MT_CASE_A,\ + totest.MT_CASE_B, totest.MT_CASE_A, totest.MT_CASE_A]) ==\ + [totest.MT_CASE_UNDETERMINED, totest.MT_CASE_A,\ + totest.MT_CASE_B, totest.MT_CASE_A] + # examples: cut out no RLO from strings + assert totest.cumulative_mass_transfer_numeric(["A", "no_RLO", "A",\ + "B", "A", "A"]) == [totest.MT_CASE_A, totest.MT_CASE_B,\ + totest.MT_CASE_A] + + def test_cumulative_mass_transfer_string(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'cumulative_integers'"): + totest.cumulative_mass_transfer_string() + # bad input + with raises(TypeError, match="object of type 'NoneType' has no len()"): + totest.cumulative_mass_transfer_string(None) + with raises(AssertionError): # missing case information + totest.cumulative_mass_transfer_string([]) + with warns(InappropriateValueWarning, match="Unknown MT case:"): + # unknown case + totest.cumulative_mass_transfer_string([-1]) + # examples: undetermined + assert totest.cumulative_mass_transfer_string(\ + [totest.MT_CASE_UNDETERMINED]) == "?" + # examples: no RLO + assert totest.cumulative_mass_transfer_string(\ + [totest.MT_CASE_NO_RLO]) == "no_RLO" + # examples: cases for both stars + for c in totest.ALL_RLO_CASES: + assert totest.cumulative_mass_transfer_string([c, 10+c]) ==\ + "case_"+totest.MT_CASE_TO_STR[c]+"1/"+\ + totest.MT_CASE_TO_STR[c]+"2" + + def test_cumulative_mass_transfer_flag(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'MT_cases'"): + totest.cumulative_mass_transfer_flag() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'copy'"): + totest.cumulative_mass_transfer_flag(None) + with warns(EvolutionWarning, match="MT case with unknown donor:"): + with warns(InappropriateValueWarning): + totest.cumulative_mass_transfer_flag([30], shift_cases=True) + # examples: + assert totest.cumulative_mass_transfer_flag([totest.MT_CASE_A,\ + totest.MT_CASE_B, 10+totest.MT_CASE_A, totest.MT_CASE_A,\ + 10+totest.MT_CASE_B, 10+totest.MT_CASE_A]) ==\ + 'case_A1/B1/A2/A1/B2/A2' + assert totest.cumulative_mass_transfer_flag([totest.MT_CASE_A,\ + totest.MT_CASE_B, 10+totest.MT_CASE_A, totest.MT_CASE_A,\ + 10+totest.MT_CASE_B, 10+totest.MT_CASE_A],\ + shift_cases=True) == 'case_A1/B1/A2/B1/B2' + + def test_get_i_He_depl(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'history'"): + totest.get_i_He_depl() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'dtype'"): + totest.get_i_He_depl(None) + with raises(TypeError, match="argument of type 'NoneType' is not"+\ + " iterable"): + totest.get_i_He_depl(totest.np.array([])) + # examples: incomplete history to search through + assert totest.get_i_He_depl(totest.np.array([[]],\ + dtype=([('surface_h1', 'f8')]))) == -1 + # examples: no he depletion + assert totest.get_i_He_depl(totest.np.array([(1.0, 0.0, 1.0, 0.0, 0.0,\ + 1.0, 1.0), (1.0, 0.0, 0.5, 0.5, 0.0, 1.0, 1.0), (1.0, 0.0, 0.2,\ + 0.2, 0.0, 1.0, 1.0)], dtype=([('surface_h1', 'f8'),\ + ('center_h1', 'f8'), ('center_he4', 'f8'),\ + ('center_c12', 'f8'), ('log_LH', 'f8'), ('log_LHe', 'f8'),\ + ('log_Lnuc', 'f8')]))) == -1 + # examples: he depletion at 1 + assert totest.get_i_He_depl(totest.np.array([(1.0, 0.0, 1.0, 0.0, 0.0,\ + 1.0, 1.0), (1.0, 0.0, 0.0, 0.5, 0.0, 1.0, 1.0), (1.0, 0.0, 0.0,\ + 0.0, 0.0, 1.0, 1.0)], dtype=([('surface_h1', 'f8'),\ + ('center_h1', 'f8'), ('center_he4', 'f8'),\ + ('center_c12', 'f8'), ('log_LH', 'f8'), ('log_LHe', 'f8'),\ + ('log_Lnuc', 'f8')]))) == 1 + + def test_calculate_Patton20_values_at_He_depl(self, SingleStar): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'star'"): + totest.calculate_Patton20_values_at_He_depl() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'state_history'"): + totest.calculate_Patton20_values_at_He_depl(None) + # examples: no history + SingleStar.co_core_mass_at_He_depletion = 0.5 + SingleStar.avg_c_in_c_core_at_He_depletion = 0.5 + SingleStar.state_history = None + totest.calculate_Patton20_values_at_He_depl(SingleStar) + assert SingleStar.co_core_mass_at_He_depletion is None + assert SingleStar.avg_c_in_c_core_at_He_depletion is None + # examples: no He_depleted in history + SingleStar.co_core_mass_at_He_depletion = 0.5 + SingleStar.avg_c_in_c_core_at_He_depletion = 0.5 + SingleStar.state_history = ["test"] + totest.calculate_Patton20_values_at_He_depl(SingleStar) + assert SingleStar.co_core_mass_at_He_depletion is None + assert SingleStar.avg_c_in_c_core_at_He_depletion is None + # examples: loop through star types with He depletion + for s,v in zip(["H-rich_Central_He_depleted",\ + "stripped_He_Central_He_depleted"], [0.1, 0.2]): + SingleStar.state_history = ["test", s, s] + SingleStar.co_core_mass_history = [0.0, v, 1.0] + SingleStar.avg_c_in_c_core_history = [0.0, v, 1.0] + totest.calculate_Patton20_values_at_He_depl(SingleStar) + assert SingleStar.co_core_mass_at_He_depletion == v + assert SingleStar.avg_c_in_c_core_at_He_depletion == v + + def test_CEE_parameters_from_core_abundance_thresholds(self, monkeypatch,\ + capsys, SingleStar): + def mock_calculate_core_boundary(donor_mass, donor_star_state,\ + profile, mc1_i=None, core_element_fraction_definition=None,\ + CO_core_in_Hrich_star=False): + return core_element_fraction_definition + def mock_calculate_lambda_from_profile(profile, donor_star_state,\ + m1_i=totest.np.nan, radius1=totest.np.nan,\ + common_envelope_option_for_lambda=\ + totest.DEFAULT_CE_OPTION_FOR_LAMBDA,\ + core_element_fraction_definition=0.1, ind_core=None,\ + common_envelope_alpha_thermal=1.0, tolerance=0.001,\ + CO_core_in_Hrich_star=False, verbose=False): + return core_element_fraction_definition,\ + core_element_fraction_definition,\ + core_element_fraction_definition + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'star'"): + totest.CEE_parameters_from_core_abundance_thresholds() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'mass'"): + totest.CEE_parameters_from_core_abundance_thresholds(None) + with raises(TypeError) as error_info: + totest.CEE_parameters_from_core_abundance_thresholds(SingleStar) + assert error_info.value.args[0] == "unsupported operand type(s) for"+\ + " ** or pow(): 'float' and 'NoneType'" + SingleStar.log_R = 0.0 + SingleStar.profile = totest.np.array([(1.0), (1.0), (1.0)],\ + dtype=([('mass', 'f8')])) + with raises(TypeError, match="argument of type 'NoneType' is not"+\ + " iterable"): + totest.CEE_parameters_from_core_abundance_thresholds(SingleStar) + # examples: missing state with profile and verbose + SingleStar.state = "test_state" + totest.CEE_parameters_from_core_abundance_thresholds(SingleStar,\ + verbose=True) + captured_out = capsys.readouterr().out + for out in ["is not what expected during the "+\ + "CEE_parameters_from_core_abundance_thresholds.",\ + "star_state", "m_core_CE_1cent,m_core_CE_10cent,"+\ + "m_core_CE_30cent,m_core_CE_pure_He_star_10cent",\ + "r_core_CE_1cent,r_core_CE_10cent,r_core_CE_30cent,"+\ + "r_core_CE_pure_He_star_10cent", "lambda_CE_1cent,"+\ + "lambda_CE_10cent,lambda_CE_30cent,"+\ + "lambda_CE_pure_He_star_10cent"]: + assert out in captured_out + for attr in ['m_core_CE_1cent', 'm_core_CE_10cent',\ + 'm_core_CE_30cent', 'm_core_CE_pure_He_star_10cent',\ + 'r_core_CE_1cent', 'r_core_CE_10cent',\ + 'r_core_CE_30cent', 'r_core_CE_pure_He_star_10cent',\ + 'lambda_CE_1cent', 'lambda_CE_10cent',\ + 'lambda_CE_30cent', 'lambda_CE_pure_He_star_10cent']: + assert totest.np.isnan(getattr(SingleStar, attr)) + monkeypatch.setattr(totest, "calculate_core_boundary",\ + mock_calculate_core_boundary) + monkeypatch.setattr(totest, "calculate_lambda_from_profile",\ + mock_calculate_lambda_from_profile) + for s in ["test_state", "H-rich_test", "stripped_He_test"]: + SingleStar.state = s + totest.CEE_parameters_from_core_abundance_thresholds(SingleStar) + assert capsys.readouterr().out == "" + for attr in ['m_core_CE_1cent', 'm_core_CE_10cent',\ + 'm_core_CE_30cent', 'm_core_CE_pure_He_star_10cent',\ + 'r_core_CE_1cent', 'r_core_CE_10cent',\ + 'r_core_CE_30cent', 'r_core_CE_pure_He_star_10cent',\ + 'lambda_CE_1cent', 'lambda_CE_10cent',\ + 'lambda_CE_30cent', 'lambda_CE_pure_He_star_10cent']: + if s == "test_state": + assert totest.np.isnan(getattr(SingleStar, attr)) + elif (("stripped_He" in s) and ("pure_He" not in attr) and\ + ("m_core" in attr)): + assert getattr(SingleStar, attr) == SingleStar.mass + elif (("stripped_He" in s) and ("pure_He" not in attr) and\ + ("r_core" in attr)): + assert getattr(SingleStar, attr) == 10**SingleStar.log_R + elif (("stripped_He" in s) and ("pure_He" not in attr) and\ + ("lambda" in attr)): + assert totest.np.isnan(getattr(SingleStar, attr)) + elif "1cent" in attr: + assert getattr(SingleStar, attr) == 0.01 + elif "10cent" in attr: + assert getattr(SingleStar, attr) == 0.1 + elif "30cent" in attr: + assert getattr(SingleStar, attr) == 0.3 + else: + assert totest.np.isnan(getattr(SingleStar, attr)) + # examples: no profile + SingleStar.profile = None + totest.CEE_parameters_from_core_abundance_thresholds(SingleStar) + for attr in ['m_core_CE_1cent', 'm_core_CE_10cent',\ + 'm_core_CE_30cent', 'm_core_CE_pure_He_star_10cent',\ + 'r_core_CE_1cent', 'r_core_CE_10cent',\ + 'r_core_CE_30cent', 'r_core_CE_pure_He_star_10cent',\ + 'lambda_CE_1cent', 'lambda_CE_10cent',\ + 'lambda_CE_30cent', 'lambda_CE_pure_He_star_10cent']: + assert totest.np.isnan(getattr(SingleStar, attr)) + + def test_initialize_empty_array(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'arr'"): + totest.initialize_empty_array() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'copy'"): + totest.initialize_empty_array(None) + # examples: + test_array = totest.np.array([(1.0, "test"), (1.0, "test")],\ + dtype=([('mass', 'f8'), ('state', 'U8')])) + empty_array = totest.initialize_empty_array(test_array) + for i in range(len(empty_array)): + assert totest.np.isnan(empty_array['mass'][i]) # nan float + assert empty_array['state'][i] == 'nan' # nan str + + def test_calculate_core_boundary(self, star_profile): + # missing argument + with raises(TypeError, match="missing 3 required positional"+\ + " arguments: 'donor_mass', 'donor_star_state', and"+\ + " 'profile'"): + totest.calculate_core_boundary() + # bad input + with raises(ValueError, match="Not possible to calculate the core"+\ + " boundary of the donor in CE"): + totest.calculate_core_boundary(None, None, None) + with warns(ApproximationWarning, match="Stellar profile columns were"+\ + " not enough to calculate the core-envelope boundaries"+\ + " for CE, entire star is now considered an envelope"): + assert totest.calculate_core_boundary(None, None, None,\ + core_element_fraction_definition=0.1) == -1 + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'dtype'"): + totest.calculate_core_boundary(None, "H-rich_Core_H_burning",\ + None, core_element_fraction_definition=0.1) + # examples: get He core in H rich star + assert totest.calculate_core_boundary(None, "H-rich_Core_H_burning",\ + star_profile, core_element_fraction_definition=0.1) == 2 + # examples: get CO core in H rich star + assert totest.calculate_core_boundary(None, "H-rich_Core_H_burning",\ + star_profile, core_element_fraction_definition=0.3,\ + CO_core_in_Hrich_star=True) == 3 + # examples: get CO core in He star without a match + assert totest.calculate_core_boundary(None,\ + "stripped_He_Core_He_burning", star_profile,\ + core_element_fraction_definition=0.1) == -1 + # examples: given core mass + test_mass = totest.np.array([1.0, 0.7, 0.4, 0.1, 0.0]) + assert totest.calculate_core_boundary(test_mass, None, None,\ + mc1_i=0.1) == 4 + + def test_period_evol_wind_loss(self): + # missing argument + with raises(TypeError, match="missing 4 required positional"+\ + " arguments: 'M_current', 'M_init', 'Mcomp', and"+\ + " 'P_init'"): + totest.period_evol_wind_loss() + # bad input + with raises(TypeError) as error_info: + totest.period_evol_wind_loss(None, None, None, None) + assert error_info.value.args[0] == "unsupported operand type(s) for"+\ + " +: 'NoneType' and 'NoneType'" + # examples: + assert totest.period_evol_wind_loss(0.5, 0.5, 0.5, 0.5) == 0.5 + assert totest.period_evol_wind_loss(1.5, 0.5, 0.5, 0.5) == 0.5/4 + assert totest.period_evol_wind_loss(0.5, 1.5, 0.5, 0.5) == 0.5*4 + assert totest.period_evol_wind_loss(0.5, 0.5, 1.5, 0.5) == 0.5 + assert totest.period_evol_wind_loss(0.5, 0.5, 0.5, 1.5) == 1.5 + + def test_separation_evol_wind_loss(self): + # missing argument + with raises(TypeError, match="missing 4 required positional"+\ + " arguments: 'M_current', 'M_init', 'Mcomp', and"+\ + " 'A_init'"): + totest.separation_evol_wind_loss() + # bad input + with raises(TypeError) as error_info: + totest.separation_evol_wind_loss(None, None, None, None) + assert error_info.value.args[0] == "unsupported operand type(s) for"+\ + " +: 'NoneType' and 'NoneType'" + # examples: + assert totest.separation_evol_wind_loss(0.5, 0.5, 0.5, 0.5) == 0.5 + assert totest.separation_evol_wind_loss(1.5, 0.5, 0.5, 0.5) == 0.5/2 + assert totest.separation_evol_wind_loss(0.5, 1.5, 0.5, 0.5) == 0.5*2 + assert totest.separation_evol_wind_loss(0.5, 0.5, 1.5, 0.5) == 0.5 + assert totest.separation_evol_wind_loss(0.5, 0.5, 0.5, 1.5) == 1.5 + + def test_period_change_stabe_MT(self): + # missing argument + with raises(TypeError, match="missing 4 required positional"+\ + " arguments: 'period_i', 'Mdon_i', 'Mdon_f', and"+\ + " 'Macc_i'"): + totest.period_change_stabe_MT() + # bad input + with raises(TypeError) as error_info: + totest.period_change_stabe_MT(None, None, None, None) + assert error_info.value.args[0] == "unsupported operand type(s) for"+\ + " -: 'NoneType' and 'NoneType'" + for a,b in zip([-1.0, 2.0, 0.0, 0.0], [0.0, 0.0, -1.0, 2.0]): + with raises(ValueError) as error_info: + totest.period_change_stabe_MT(0.5, 0.5, 0.5, 0.5, alpha=a,\ + beta=b) + assert error_info.value.args[0] == "In period_change_stabe_MT,"+\ + f" mass transfer efficiencies, alpha, beta: {a}, {b} are"+\ + " not in the [0-1] range." + with raises(ValueError, match="Donor gains mass from 0.5 to 1.5"): + totest.period_change_stabe_MT(0.5, 0.5, 1.5, 0.5) + # examples: + assert totest.period_change_stabe_MT(0.5, 0.5, 0.5, 0.5) == 0.5 + assert totest.period_change_stabe_MT(1.5, 0.5, 0.5, 0.5) == 1.5 + assert totest.period_change_stabe_MT(0.5, 1.5, 0.5, 0.5) == 0.5 + assert totest.period_change_stabe_MT(0.5, 0.5, 0.5, 1.5) == 0.5 + assert totest.period_change_stabe_MT(0.5, 1.0, 0.5, 1.0) ==\ + approx(1.18518518519, abs=6e-12) + assert totest.period_change_stabe_MT(0.5, 0.5, 0.5, 0.5, beta=1.0) ==\ + 0.5 + assert totest.period_change_stabe_MT(1.5, 0.5, 0.5, 0.5, beta=1.0) ==\ + 1.5 + assert totest.period_change_stabe_MT(0.5, 1.5, 0.5, 0.5, beta=1.0) ==\ + approx(0.13385261754, abs=6e-12) + assert totest.period_change_stabe_MT(0.5, 0.5, 0.5, 1.5, beta=1.0) ==\ + 0.5 + + def test_linear_interpolation_between_two_cells(self, capsys): + # missing argument + with raises(TypeError, match="missing 3 required positional"+\ + " arguments: 'array_y', 'array_x', and 'x_target'"): + totest.linear_interpolation_between_two_cells() + # bad input + with raises(TypeError, match="'>=' not supported between instances"+\ + " of 'NoneType' and 'NoneType'"): + totest.linear_interpolation_between_two_cells(None, None, None) + # examples: automatic interpolation + test_array_x = totest.np.array([0.0, 0.2, 1.0]) + test_array_y = totest.np.array([1.0, 0.4, 0.0]) + assert totest.linear_interpolation_between_two_cells(test_array_y,\ + test_array_x, 0.1) == 0.7 + # examples: interpolation over multiple cells with verbose + assert totest.linear_interpolation_between_two_cells(test_array_y,\ + test_array_x, 0.1, top=2, bot=0, verbose=True) == 0.9 + captured_out = capsys.readouterr().out + for out in ["linear interpolation",\ + "x_target, top, bot, len(array_x)",\ + "x_top, x_bot, y_top, y_bot, y_target"]: + assert out in captured_out + # examples: extrapolation + assert totest.linear_interpolation_between_two_cells(test_array_y,\ + test_array_x, 0.1, top=2) == 0.45 + assert totest.linear_interpolation_between_two_cells(test_array_y,\ + test_array_x, 0.1, bot=1) == 0.45 + with warns(ReplaceValueWarning, match="top=3 is too large, use last"+\ + " element in array_y"): + with warns(InterpolationWarning): + assert totest.linear_interpolation_between_two_cells(\ + test_array_y, test_array_x, 0.1, top=3) == 0.0 + with warns(ReplaceValueWarning, match="bot=-1 is too small, use"+\ + " first element"): + with warns(InterpolationWarning): + assert totest.linear_interpolation_between_two_cells(\ + test_array_y, test_array_x, 0.1, top=0) == 1.0 + with warns(InterpolationWarning, match="bot=2 is too large: use y at"+\ + " top=1"): + assert totest.linear_interpolation_between_two_cells(\ + test_array_y, test_array_x, 0.1, top=1, bot=2) == 0.4 + with warns(InterpolationWarning, match="array_x too short, use y at"+\ + " top=3"): + assert totest.linear_interpolation_between_two_cells(\ + totest.np.array([1.0, 0.4, 0.0, -1.0]), test_array_x, 0.1,\ + top=3) == -1.0 + + def test_calculate_lambda_from_profile(self, star_profile, capsys): + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'profile' and 'donor_star_state'"): + totest.calculate_lambda_from_profile() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'dtype'"): + totest.calculate_lambda_from_profile(None, None) + with raises(ValueError, match="state test not supported in CEE"): + totest.calculate_lambda_from_profile(star_profile, "test",\ + ind_core=1) + with raises(ValueError, match="lambda_CE has a negative value"): + with warns(EvolutionWarning, match="Ebind_i of the envelope is"+\ + " positive"): + totest.calculate_lambda_from_profile(star_profile,\ + "H-rich_test", ind_core=1) + # examples: get He core in H rich star + lambda_CE, Mc, Rc = totest.calculate_lambda_from_profile(star_profile,\ + "test", common_envelope_alpha_thermal=0.1) + assert lambda_CE == approx(3.16722966582e+33, abs=6e+21) + assert Mc == approx(0.0, abs=6e-12) + assert Rc == approx(0.0, abs=6e-12) + lambda_CE, Mc, Rc = totest.calculate_lambda_from_profile(star_profile,\ + "test", ind_core=0,\ + common_envelope_alpha_thermal=0.1) + assert totest.np.isnan(lambda_CE) + assert Mc == star_profile['mass'][0] + assert Rc == star_profile['radius'][0] + # examples: He core in H rich star + lambda_CE, Mc, Rc = totest.calculate_lambda_from_profile(star_profile,\ + "H-rich_test", ind_core=2,\ + common_envelope_alpha_thermal=0.1, verbose=True) + assert lambda_CE == approx(3.35757244514e+33, abs=6e+21) + assert Mc == 0.1 + assert Rc == 0.1 + captured_out = capsys.readouterr().out + for out in ["lambda_CE_top, lambda_CE_bot",\ + "m1_i, radius1, len(profile) vs ind_core, mc1_i, rc1_i",\ + "Ebind_i from profile ", "lambda_CE "]: + assert out in captured_out + # examples: CO core in H rich star + lambda_CE, Mc, Rc = totest.calculate_lambda_from_profile(star_profile,\ + "H-rich_test", ind_core=2,\ + common_envelope_alpha_thermal=0.1,\ + CO_core_in_Hrich_star=True) + assert lambda_CE == approx(4.25658010995e+32, abs=6e+20) + assert Mc == approx(1.03333333333, abs=6e-12) + assert Rc == approx(1.03333333333, abs=6e-12) + # examples: CO core in He rich star + assert totest.calculate_lambda_from_profile(star_profile,\ + "H-rich_test", ind_core=2, common_envelope_alpha_thermal=0.1,\ + CO_core_in_Hrich_star=True) ==\ + totest.calculate_lambda_from_profile(star_profile,\ + "stripped_He_test", ind_core=2,\ + common_envelope_alpha_thermal=0.1) + + def test_get_mass_radius_dm_from_profile(self, star_profile): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'profile'"): + totest.get_mass_radius_dm_from_profile() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'dtype'"): + totest.get_mass_radius_dm_from_profile(None) + for f in star_profile.dtype.names: + with raises(ValueError, match="One or many of the mass and/or"+\ + " radius needed columns in the profile is not"+\ + " provided for the CEE"): + totest.get_mass_radius_dm_from_profile(star_profile[[f]]) + # examples: profile with radius + with warns(ClassificationWarning, match="Donor mass from the binary"+\ + " class object and the profile do not agree"): + with warns(ClassificationWarning, match="Donor radius from the"+\ + " binary class object and the profile do not agree"): + d_mass, d_radius, d_dm =\ + totest.get_mass_radius_dm_from_profile(star_profile[['mass',\ + 'radius']]) + assert totest.np.array_equal(d_mass, star_profile['mass']) + assert totest.np.array_equal(d_radius, star_profile['radius']) + assert totest.np.array_equal(d_dm, star_profile['dm']) + # get total mass and radius + M = star_profile['mass'].max() + R = star_profile['radius'].max() + # examples: profile with log_R + d_mass, d_radius, d_dm = totest.get_mass_radius_dm_from_profile(\ + star_profile[['mass', 'log_R']], m1_i = M,\ + radius1 = R) + assert totest.np.array_equal(d_mass, star_profile['mass']) + assert totest.np.array_equal(d_radius, star_profile['radius']) + assert totest.np.array_equal(d_dm, star_profile['dm']) + # examples: profile with radius and dm (in grams) + d_mass, d_radius, d_dm = totest.get_mass_radius_dm_from_profile(\ + star_profile[['mass', 'dm', 'radius']],\ + m1_i = M, radius1 = R) + assert totest.np.array_equal(d_mass, star_profile['mass']) + assert totest.np.array_equal(d_radius, star_profile['radius']) + # convert Msun to grams + d_dm = d_dm * totest.const.Msun + assert totest.np.array_equal(d_dm, star_profile['dm']) + + def test_get_internal_energy_from_profile(self, star_profile, monkeypatch): + def mock_calculate_H2recombination_energy(profile, tolerance=0.001): + return totest.np.array(len(profile)*[1.0e+99]) + def mock_calculate_recombination_energy(profile, tolerance=0.001): + return totest.np.array(len(profile)*[1.0e+99]) + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'common_envelope_option_for_lambda' and"+\ + " 'profile'"): + totest.get_internal_energy_from_profile() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'dtype'"): + totest.get_internal_energy_from_profile(None, None) + with raises(ValueError, match="unsupported:"+\ + " common_envelope_option_for_lambda = test"): + totest.get_internal_energy_from_profile("test",\ + star_profile[['energy']]) + bad_profile = totest.np.array([-1.0, -1.0, -1.0, -1.0],\ + dtype=([('energy', 'f8')])) + bad_profile2 = totest.np.empty((2,), dtype=[(f, 'f8') for f in [\ + 'x_mass_fraction_H', 'y_mass_fraction_He',\ + 'neutral_fraction_H', 'neutral_fraction_He',\ + 'avg_charge_He', 'energy']]) + bad_profile2['x_mass_fraction_H'] = totest.np.array([-1.0, -1.0]) + bad_profile2['y_mass_fraction_He'] = totest.np.array([-1.0, -1.0]) + bad_profile2['neutral_fraction_H'] = totest.np.array([0.5, 0.5]) + bad_profile2['neutral_fraction_He'] = totest.np.array([0.5, 0.5]) + bad_profile2['avg_charge_He'] = totest.np.array([0.5, 0.5]) + bad_profile2['energy'] = totest.np.array([0.5, 0.5]) + for CEOFL in ["lambda_from_profile_gravitational_plus_internal",\ + "lambda_from_profile_gravitational_plus_internal_minus"+\ + "_recombination"]: + with raises(ValueError, match="CEE problem calculating internal"+\ + " energy, giving negative values."): + totest.get_internal_energy_from_profile(CEOFL, bad_profile) + with monkeypatch.context() as m: + m.setattr(totest, "calculate_H2recombination_energy",\ + mock_calculate_H2recombination_energy) + m.setattr(totest, "calculate_recombination_energy",\ + mock_calculate_recombination_energy) + with raises(ValueError) as error_info: + totest.get_internal_energy_from_profile(CEOFL,\ + star_profile[['energy']]) + assert error_info.value.args[0] == "CEE problem calculating"+\ + " recombination (and H2 recombination) energy,"+\ + " remaining internal energy giving negative values." + # examples: no internal energy + assert totest.np.array_equal(totest.np.array([0, 0, 0, 0]),\ + totest.get_internal_energy_from_profile(\ + "lambda_from_profile_gravitational", star_profile[['radius']])) + with warns(ApproximationWarning, match="Profile does not include"+\ + " internal energy -- proceeding with "+\ + "'lambda_from_profile_gravitational'"): + assert totest.np.array_equal(totest.np.array([0, 0, 0, 0]),\ + totest.get_internal_energy_from_profile("test",\ + star_profile[['radius']])) + # examples: with internal energy + assert totest.np.allclose(totest.get_internal_energy_from_profile(\ + "lambda_from_profile_gravitational_plus_internal",\ + star_profile), totest.np.array([9.99850443e+15, 4.99893173e+15,\ + 9.99786347e+14, 9.99786347e+12])) + + def test_calculate_H2recombination_energy(self, star_profile): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'profile'"): + totest.calculate_H2recombination_energy() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'dtype'"): + totest.calculate_H2recombination_energy(None) + with raises(ValueError, match="CEE problem calculating H2"+\ + " recombination energy, giving negative values"): + bad_profile = totest.np.array([-1.0, -1.0, -1.0, -1.0],\ + dtype=([('x_mass_fraction_H', 'f8')])) + totest.calculate_H2recombination_energy(bad_profile) + # examples: profile with Hydrogen mass fraction + assert totest.np.allclose(totest.np.array([1.49557421e+12,\ + 1.06826729e+12, 2.13653458e+11, 2.13653458e+09]),\ + totest.calculate_H2recombination_energy(\ + star_profile[['x_mass_fraction_H']])) + # examples: profile without Hydrogen mass fraction + with warns(ApproximationWarning, match="Profile does not include"+\ + " Hydrogen mass fraction calculate H2 recombination"+\ + " energy -- H2 recombination energy is assumed 0"): + assert totest.np.array_equal(totest.np.array([0, 0, 0, 0]),\ + totest.calculate_H2recombination_energy(\ + star_profile[['radius']])) + + def test_calculate_recombination_energy(self, star_profile): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'profile'"): + totest.calculate_recombination_energy() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'dtype'"): + totest.calculate_recombination_energy(None) + with raises(ValueError, match="CEE problem calculating"+\ + " recombination energy, giving negative values"): + bad_profile = totest.np.empty((2,), dtype=[(f, 'f8') for f in [\ + 'x_mass_fraction_H', 'y_mass_fraction_He',\ + 'neutral_fraction_H', 'neutral_fraction_He',\ + 'avg_charge_He']]) + bad_profile['x_mass_fraction_H'] = totest.np.array([-1.0, -1.0]) + bad_profile['y_mass_fraction_He'] = totest.np.array([-1.0, -1.0]) + bad_profile['neutral_fraction_H'] = totest.np.array([0.5, 0.5]) + bad_profile['neutral_fraction_He'] = totest.np.array([0.5, 0.5]) + bad_profile['avg_charge_He'] = totest.np.array([0.5, 0.5]) + totest.calculate_recombination_energy(bad_profile) + # examples: profile with mass fraction and ionization information + assert totest.np.allclose(totest.np.array([0.00000000e+00,\ + 4.73639308e+12, 7.53791976e+12, 3.29724895e+12]),\ + totest.calculate_recombination_energy(\ + star_profile[['x_mass_fraction_H', 'y_mass_fraction_He', 'neutral_fraction_H', 'neutral_fraction_He', 'avg_charge_He']])) + # examples: profile without mass fraction and ionization information + with warns(ApproximationWarning, match="Profile does not include"+\ + " mass fractions and ionizations of elements to calculate"+\ + " recombination energy -- recombination energy is assumed"+\ + " 0"): + assert totest.np.array_equal(totest.np.array([0, 0, 0, 0]),\ + totest.calculate_recombination_energy(\ + star_profile[['radius']])) + + def test_profile_recomb_energy(self): + # missing argument + with raises(TypeError, match="missing 5 required positional"+\ + " arguments: 'x_mass_fraction_H', 'y_mass_fraction_He',"+\ + " 'frac_HII', 'frac_HeII', and 'frac_HeIII'"): + totest.profile_recomb_energy() + # bad input + with raises(TypeError) as error_info: + totest.profile_recomb_energy(None, None, None, None, None) + assert error_info.value.args[0] == "unsupported operand type(s) for"+\ + " *: 'float' and 'NoneType'" + # examples: + assert totest.profile_recomb_energy(0.5, 0.5, 0.5, 0.5, 0.5) ==\ + approx(9.49756867371e+12, abs=6e+0) + assert totest.profile_recomb_energy(0.1, 0.5, 0.5, 0.5, 0.5) ==\ + approx(6.89384394360e+12, abs=6e+0) + assert totest.profile_recomb_energy(0.5, 0.1, 0.5, 0.5, 0.5) ==\ + approx(4.50323846485e+12, abs=6e+0) + assert totest.profile_recomb_energy(0.5, 0.5, 0.1, 0.5, 0.5) ==\ + approx(6.89384394360e+12, abs=6e+0) + assert totest.profile_recomb_energy(0.5, 0.5, 0.5, 0.1, 0.5) ==\ + approx(8.31217893947e+12, abs=6e+0) + assert totest.profile_recomb_energy(0.5, 0.5, 0.5, 0.5, 0.1) ==\ + approx(5.68862819909e+12, abs=6e+0) + + def test_calculate_binding_energy(self, star_profile, capsys): + # missing argument + with raises(TypeError, match="missing 7 required positional"+\ + " arguments: 'donor_mass', 'donor_radius', 'donor_dm',"+\ + " 'specific_internal_energy', 'ind_core',"+\ + " 'factor_internal_energy', and 'verbose'"): + totest.calculate_binding_energy() + # bad input + with raises(TypeError, match="'NoneType' object cannot be"+\ + " interpreted as an integer"): + totest.calculate_binding_energy(None, None, None, None, None,\ + None, None) + with raises(ValueError, match="CEE problem calculating"+\ + " gravitational energy, giving positive values"): + totest.calculate_binding_energy(-1.0*star_profile['mass'],\ + star_profile['radius'],\ + star_profile['dm'],\ + star_profile['energy'], 1, 0.1,\ + False) + # examples: + with warns(EvolutionWarning, match="Ebind_i of the envelope is"+\ + " positive"): + assert totest.calculate_binding_energy(star_profile['mass'],\ + -1.0e+60*star_profile['radius'], star_profile['dm'],\ + star_profile['energy'], 1, 0.5, False) == 4.973e+48 + assert totest.calculate_binding_energy(star_profile['mass'],\ + star_profile['radius'], star_profile['dm'],\ + star_profile['energy'], 2, 0.5, False) ==\ + approx(3.547071313426e+48, abs=6e+36) + assert totest.calculate_binding_energy(star_profile['mass'],\ + star_profile['radius'], star_profile['dm'],\ + star_profile['energy'], 1, 0.1, True) ==\ + approx(-9.02693714763e+47, abs=6e+35) + captured_out = capsys.readouterr().out + for out in ["integration of gravitational energy surface to core"+\ + " [Grav_energy], integration of internal energy surface"+\ + " to core [U_i] (0 if not taken into account)",\ + "Ebind = Grav_energy + factor_internal_energy*U_i : "]: + assert out in captured_out + + def test_calculate_Mejected_for_integrated_binding_energy(self,\ + star_profile): + # missing argument + with raises(TypeError, match="missing 4 required positional"+\ + " arguments: 'profile', 'Ebind_threshold', 'mc1_i', and"+\ + " 'rc1_i'"): + totest.calculate_Mejected_for_integrated_binding_energy() + # bad input + with raises(AttributeError, match="'NoneType' object has no"+\ + " attribute 'dtype'"): + totest.calculate_Mejected_for_integrated_binding_energy(None,\ + None, None, None) + # examples: + # get total mass and radius + M = star_profile['mass'].max() + R = star_profile['radius'].max() + assert totest.calculate_Mejected_for_integrated_binding_energy(\ + star_profile, 1.0, 1.0, 1.0, m1_i=M, radius1=R) == 0.0 + with warns(EvolutionWarning, match="partial mass ejected is greater"+\ + " than the envelope mass"): + assert totest.calculate_Mejected_for_integrated_binding_energy(\ + star_profile, 1.0e+50, 1.0, 1.0, m1_i=M, radius1=R) == 0.0 + + def test_convert_metallicity_to_string(self): + # missing argument + with raises(TypeError, match="missing 1 required positional"+\ + " argument: 'Z'"): + totest.convert_metallicity_to_string() + # bad input + with raises(ValueError, match="Metallicity None not supported!"+\ + " Available metallicities in POSYDON v2 are"): + totest.convert_metallicity_to_string(None) + # examples: + for Z,s in zip([2e+00, 1e+00, 4.5e-01, 2e-01, 1e-01, 1e-02, 1e-03,\ + 1e-04], ['2e+00', '1e+00', '4.5e-01', '2e-01',\ + '1e-01', '1e-02', '1e-03', '1e-04']): + assert totest.convert_metallicity_to_string(Z) == s + + def test_rotate(self): + # missing argument + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'axis' and 'angle'"): + totest.rotate() + # bad input + with raises(TypeError, match="object of type 'NoneType' has no len()"): + totest.rotate(None, None) + with raises(ValueError, match="axis should be of dimension 3"): + totest.rotate(totest.np.array([0]), 0.0) + with raises(ValueError, match="axis is a point"): + totest.rotate(totest.np.array([0, 0, 0]), 0.0) + # examples: + for x in range(3): + for y in range(3): + for z in range(3): + if x!=0 or y!=0 or z!=0: + # angle=0 -> no rotation + assert totest.np.array_equal(totest.rotate(\ + totest.np.array([x, y, z]), 0.0),\ + totest.np.array([[1, 0, 0], [0, 1, 0],\ + [0, 0, 1]])) + # inverse rotation around inverted axis + assert totest.np.array_equal(totest.rotate(\ + totest.np.array([-x, -y, -z]), -1.0),\ + totest.rotate(totest.np.array([x, y, z]), 1.0)) + # examples: angle=1.0 + s = totest.np.sin(1.0) + c = totest.np.cos(1.0) + assert totest.np.array_equal(totest.rotate(totest.np.array([1, 0, 0]),\ + 1.0), totest.np.array([[1, 0, 0], [0, c, -s], [0, s, c]])) + assert totest.np.array_equal(totest.rotate(totest.np.array([0, 1, 0]),\ + 1.0), totest.np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]])) + assert totest.np.array_equal(totest.rotate(totest.np.array([0, 0, 1]),\ + 1.0), totest.np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])) + assert totest.np.allclose(totest.rotate(totest.np.array([1, 1, 0]),\ + 1.0), totest.np.array([[0.77015115, 0.22984885, 0.59500984],\ + [0.22984885, 0.77015115, -0.59500984], [-0.59500984,\ + 0.59500984, 0.54030231]])) + assert totest.np.allclose(totest.rotate(totest.np.array([1, 0, 1]),\ + 1.0), totest.np.array([[0.77015115,-0.59500984, 0.22984885],\ + [0.59500984, 0.54030231, -0.59500984], [0.22984885,\ + 0.59500984, 0.77015115]])) + assert totest.np.allclose(totest.rotate(totest.np.array([0, 1, 1]),\ + 1.0), totest.np.array([[0.54030231,-0.59500984, 0.59500984],\ + [0.59500984, 0.77015115, 0.22984885], [-0.59500984,\ + 0.22984885, 0.77015115]])) + + +class TestPchipInterpolator2: + @fixture + def PchipInterpolator2(self): + # initialize an instance of the class with defaults + return totest.PchipInterpolator2([0.0, 1.0], [1.0, 0.0]) + + @fixture + def PchipInterpolator2_True(self): + # initialize an instance of the class with defaults + return totest.PchipInterpolator2([0.0, 1.0], [-1.0, 0.0],\ + positive=True) + + # test the PchipInterpolator2 class + def test_init(self, PchipInterpolator2, PchipInterpolator2_True): + assert isroutine(PchipInterpolator2.__init__) + # check that the instance is of correct type and all code in the + # __init__ got executed: the elements are created and initialized + assert isinstance(PchipInterpolator2, totest.PchipInterpolator2) + assert isinstance(PchipInterpolator2.interpolator,\ + totest.PchipInterpolator) + assert PchipInterpolator2.positive == False + assert PchipInterpolator2_True.positive + + def test_call(self, PchipInterpolator2, PchipInterpolator2_True): + assert isroutine(PchipInterpolator2.__call__) + assert PchipInterpolator2(0.1) == 0.9 + assert PchipInterpolator2_True(0.1) == 0.0 + diff --git a/posydon/unit_tests/utils/test_ignorereason.py b/posydon/unit_tests/utils/test_ignorereason.py index bfa34606c..fdc725633 100644 --- a/posydon/unit_tests/utils/test_ignorereason.py +++ b/posydon/unit_tests/utils/test_ignorereason.py @@ -36,9 +36,9 @@ class TestValues: # check that the values fit def test_value_IGNORE_REASONS_PRIORITY(self): v_last = None - for v in ['ignored_no_history1', 'ignored_no_binary_history', - 'corrupted_history1', 'corrupted_binary_history', - 'corrupted_history2', 'ignored_scrubbed_history', + for v in ['ignored_no_history1', 'ignored_no_binary_history',\ + 'corrupted_history1', 'corrupted_binary_history',\ + 'corrupted_history2', 'ignored_scrubbed_history',\ 'ignored_no_final_profile', 'ignored_no_RLO']: # check required values assert v in totest.IGNORE_REASONS_PRIORITY, "missing entry" diff --git a/posydon/unit_tests/utils/test_posydonwarning.py b/posydon/unit_tests/utils/test_posydonwarning.py index 829aba3e3..fc95b4218 100644 --- a/posydon/unit_tests/utils/test_posydonwarning.py +++ b/posydon/unit_tests/utils/test_posydonwarning.py @@ -5,6 +5,11 @@ "Matthias Kruckow " ] +# ensure that python forgets about previous imports of posydonwarning, e.g. in +# other tests, before importing it here +from sys import modules as sys_modules +sys_modules.pop('posydon.utils.posydonwarning') + # import the module which will be tested import posydon.utils.posydonwarning as totest @@ -186,6 +191,12 @@ def test_get_stats(self): assert totest.get_stats() == totest._POSYDON_WARNINGS_REGISTRY def test_print_stats(self, capsys, clear_registry): + # empyt the global POSYDON warnings registry before this test + keys = [] + for k in totest._POSYDON_WARNINGS_REGISTRY: + keys.append(k) + for k in keys: + del totest._POSYDON_WARNINGS_REGISTRY[k] # no warnings to print totest.print_stats() assert "No POSYDON warnings occured.\n" == capsys.readouterr().out diff --git a/posydon/utils/common_functions.py b/posydon/utils/common_functions.py index 25864e607..8a51902e9 100644 --- a/posydon/utils/common_functions.py +++ b/posydon/utils/common_functions.py @@ -18,6 +18,7 @@ import os import numpy as np +import pandas as pd from scipy.interpolate import interp1d from scipy.optimize import newton from scipy.integrate import quad @@ -299,7 +300,7 @@ def eddington_limit(binary, idx=-1): Returns ------- - list + tuple The Eddington accretion limit and radiative efficiency in solar units. """ @@ -332,10 +333,10 @@ def eddington_limit(binary, idx=-1): else: raise ValueError('COtype must be "BH", "NS", or "WD"') - if surface_h1[i] is None: + if pd.isna(surface_h1[i]): surface_h1[i] = 0.7155 if state_acc[i] == "BH": - r_isco = 6 + r_isco = 6 #TODO: get r_isco as input/calculate from spin # m_ini is the accretor mass at zero spin m_ini = m_acc[i] * np.sqrt(r_isco / 6) eta = 1 - np.sqrt(1 - (min(m_acc[i], @@ -371,7 +372,7 @@ def beaming(binary): Returns ------- - list + tuple The super-Eddington isotropic-equivalent accretion rate and beaming factor respcetively in solar units. @@ -383,8 +384,12 @@ def beaming(binary): """ mdot_edd = eddington_limit(binary, idx=-1)[0] - rlo_mdot = 10**binary.lg_mtransfer_rate + if binary.lg_mtransfer_rate is None: + rlo_mdot = np.nan + else: + rlo_mdot = 10**binary.lg_mtransfer_rate + print(rlo_mdot, mdot_edd) if rlo_mdot >= mdot_edd: if rlo_mdot > 8.5 * mdot_edd: # eq. 8 in King A. R., 2009, MNRAS, 393, L41-L44 @@ -395,7 +400,7 @@ def beaming(binary): mdot_beam = mdot_edd * (1 + np.log(rlo_mdot / mdot_edd)) / b else: b = 1 - mdot_beam = 10**binary.lg_mtransfer_rate + mdot_beam = rlo_mdot return mdot_beam, b @@ -456,6 +461,7 @@ def bondi_hoyle(binary, accretor, donor, idx=-1, wind_disk_criteria=True, ecc = np.atleast_1d( np.asanyarray([*binary.eccentricity_history, binary.eccentricity], dtype=float)[idx]) + #TODO: use stars in the binary as donor and accretor m_acc = np.atleast_1d( np.asanyarray([*accretor.mass_history, accretor.mass], dtype=float)[idx]) @@ -661,7 +667,7 @@ def inverse_sampler(x, y, size=1): # if nan values found, then flat CDF for which the inverse is undefined... where_nan = np.where(~np.isfinite(sample)) - n_where_nan = len(where_nan) + n_where_nan = len(where_nan[0]) # ... in that case, simply sample randomly from each flat bin! if n_where_nan: assert np.all(dy_bins[where_nan] == 0) @@ -1039,7 +1045,8 @@ def get_binary_state_and_event_and_mt_case(binary, interpolation_class=None, verbose=verbose) # convert to strings mt_flag_1_str = cumulative_mass_transfer_string([mt_flag_1]) - mt_flag_2_str = cumulative_mass_transfer_string([mt_flag_2]) + mt_flag_2_str = cumulative_mass_transfer_string([mt_flag_2+10 if mt_flag_2\ + in ALL_RLO_CASES else mt_flag_2]) rlof1 = mt_flag_1 in ALL_RLO_CASES rlof2 = mt_flag_2 in ALL_RLO_CASES @@ -1048,7 +1055,10 @@ def get_binary_state_and_event_and_mt_case(binary, interpolation_class=None, if rlof1 and rlof2: # contact condition result = ['contact', None, 'None'] if interpolation_class == 'unstable_MT': - result = ['contact', 'oCE1', 'None'] + if rl_overflow1>=rl_overflow2: # star 1 initiated CE + result = ['contact', 'oCE1', 'None'] + else: # star 2 initiated CE + result = ['contact', 'oCE2', 'None'] elif no_rlof: # no MT in any star result = ['detached', None, 'None'] elif rlof1 and not rlof2: # only in star 1 @@ -1187,7 +1197,7 @@ def He_MS_lifetime(mass): he_t_ms = 10**(-2.6094 * np.log10(mass) + 8.7855) elif mass >= 10.0 and mass < 100.0: he_t_ms = 10**(-0.69897 * np.log10(mass) + 6.875) - elif mass >= 100.0: + else: # mass >= 100.0 he_t_ms = 3 * 10 ** 5 return he_t_ms @@ -1479,7 +1489,7 @@ def cumulative_mass_transfer_string(cumulative_integers): caseA/B/A : case A, then B, and A again (although unphysical). """ - assert len(cumulative_integers) != 0 + assert len(cumulative_integers) > 0 result = "" added_case_word = False for integer in cumulative_integers: @@ -1487,7 +1497,7 @@ def cumulative_mass_transfer_string(cumulative_integers): result += "?" elif integer == MT_CASE_NO_RLO: result += "no_RLO" - else: + elif ((integer in MT_CASE_TO_STR) or (integer-10 in MT_CASE_TO_STR)): if not added_case_word: result += "case_" added_case_word = True @@ -1497,6 +1507,9 @@ def cumulative_mass_transfer_string(cumulative_integers): result += MT_CASE_TO_STR[integer] + '1' # from star 1 else: result += MT_CASE_TO_STR[integer-10] + '2' # from star 2 + else: + Pwarn("Unknown MT case: {}".format(integer),\ + "InappropriateValueWarning") return result @@ -1541,12 +1554,13 @@ def cumulative_mass_transfer_flag(MT_cases, shift_cases=False): case_2_min = MT else: # unknown donor - Pwarn("MT case with unknown donor: {}".format(MT), "EvolutionWarning") + Pwarn("MT case with unknown donor: {}".format(MT),\ + "EvolutionWarning") corrected_MT_cases.append(MT) else: corrected_MT_cases = MT_cases.copy() return cumulative_mass_transfer_string( - cumulative_mass_transfer_numeric(MT_cases) + cumulative_mass_transfer_numeric(corrected_MT_cases) ) @@ -1674,6 +1688,7 @@ def CEE_parameters_from_core_abundance_thresholds(star, verbose=False): """ mass = star.mass radius = 10.**star.log_R + star_state = star.state m_core_CE_1cent = 0.0 m_core_CE_10cent = 0.0 m_core_CE_30cent = 0.0 @@ -1686,7 +1701,6 @@ def CEE_parameters_from_core_abundance_thresholds(star, verbose=False): if profile is not None and isinstance(profile, np.ndarray): mass_prof = profile["mass"] - star_state = star.state m_core = 0.0 r_core = 0.0 @@ -1822,6 +1836,7 @@ def initialize_empty_array(arr): res[colname] = np.nan if np.issubsctype(res[colname], str): res[colname] = np.nan + #TODO: handle h5py.string_dtype() return res @@ -1908,8 +1923,9 @@ def calculate_core_boundary(donor_mass, # ind_core=np.argmax(element[::-1]>=core_element_fraction_definition) else: ind_core = -1 - Pwarn("Stellar profile columns were not enough to calculate the core-envelope " - "boundaries for CE, entire star is now considered an envelope", "ApproximationWarning") + Pwarn("Stellar profile columns were not enough to calculate the"+\ + " core-envelope boundaries for CE, entire star is now"+\ + " considered an envelope", "ApproximationWarning") return ind_core # starting from the surface, both conditions become True when element @@ -1918,6 +1934,7 @@ def calculate_core_boundary(donor_mass, both_conditions = ( element <= core_element_fraction_definition).__and__( element_core >= core_element_high_fraction_definition) + print(both_conditions) if not np.any(both_conditions): # the whole star is an envelope, from surface towards the core # the "both_conditions" never becomes True @@ -2030,11 +2047,14 @@ def period_change_stabe_MT(period_i, Mdon_i, Mdon_f, Macc_i, """ DM_don = Mdon_i - Mdon_f # mass lost from donor (>0) + if DM_don < 0: + raise ValueError("Donor gains mass from {} to {}".format(Mdon_i, + Mdon_f)) Macc_f = Macc_i + (1.-beta)*(1.-alpha)*DM_don if alpha < 0.0 or beta < 0.0 or alpha > 1.0 or beta > 1.0: raise ValueError("In period_change_stabe_MT, mass transfer " - "efficiencies, alpha, beta {}{} are not in the [0-1] " - "range.".format(alpha, beta)) + "efficiencies, alpha, beta: {}, {} are not in the " + "[0-1] range.".format(alpha, beta)) if beta != 1.0: # Eq. 7 of Sorensen+Fragos et al. 2017 period_f = (period_i * (Mdon_f/Mdon_i)**(3.*(alpha-1.)) * (Macc_f/Macc_i)**(3./(beta-1.)) @@ -2052,26 +2072,37 @@ def period_change_stabe_MT(period_i, Mdon_i, Mdon_f, Macc_i, def linear_interpolation_between_two_cells(array_y, array_x, x_target, top=None, bot=None, verbose=False): """Interpolate quantities between two star profile shells.""" - if ((np.isnan(top) or top is None) and (np.isnan(bot) or bot is None)): + if ((top is None or np.isnan(top)) and (bot is None or np.isnan(bot))): top = np.argmax(array_x >= x_target) bot = top - 1 - elif np.isnan(bot) or bot is None: + elif bot is None or np.isnan(bot): bot = top - 1 - elif np.isnan(top) or top is None: + elif top is None or np.isnan(top): top = bot + 1 - if top > len(array_x): - y_target = array_y[top] + if top >= len(array_y): + Pwarn("top={} is too large, use last element in array_y".format(top), + "ReplaceValueWarning") + top = len(array_y)-1 + if top >= len(array_x): + Pwarn("array_x too short, use y at top={}".format(top), + "InterpolationWarning") + return array_y[top] if bot < 0: + Pwarn("bot={} is too small, use first element".format(bot), + "ReplaceValueWarning") bot = 0 if top == bot: y_target = array_y[top] - Pwarn("linear interpolation occured between the same point", "InterpolationWarning") - if verbose: - print("linear interpolation, but at the edge") - print("x_target,top, bot, len(array_x), y_target", - x_target, top, bot, len(array_x), y_target) + Pwarn("linear interpolation occured between the same point: x_target," + " top, bot, len(array_x), y_target = {}, {}, {}, {}, {}".format(\ + x_target, top, bot, len(array_x), y_target), + "InterpolationWarning") + elif bot > top: + y_target = array_y[top] + Pwarn("bot={} is too large: use y at top={}".format(bot, top), + "InterpolationWarning") else: x_top = array_x[top] x_bot = array_x[bot] @@ -2085,7 +2116,7 @@ def linear_interpolation_between_two_cells(array_y, array_x, x_target, if verbose: print("linear interpolation") - print("x_target,top, bot, len(array_x)", + print("x_target, top, bot, len(array_x)", x_target, top, bot, len(array_x)) print("x_top, x_bot, y_top, y_bot, y_target", x_top, x_bot, y_top, y_bot, y_target) @@ -2199,6 +2230,9 @@ def calculate_lambda_from_profile( elem_prof = profile["y_mass_fraction_He"] elif "stripped_He" in donor_star_state: elem_prof = profile["y_mass_fraction_He"] + else: + raise ValueError("state {} not supported in CEE"\ + .format(donor_star_state)) mc1_i = linear_interpolation_between_two_cells( donor_mass, elem_prof, core_element_fraction_definition, ind_core, ind_core-1, verbose) @@ -2276,7 +2310,7 @@ def get_mass_radius_dm_from_profile(profile, m1_i=0.0, if ("radius" in profile.dtype.names): donor_radius = profile["radius"] - elif ("log_R" in profile.dtype.names): + else: #if ("log_R" in profile.dtype.names): donor_radius = 10**profile["log_R"] # checking if mass of profile agrees with the mass of the binary object @@ -2397,6 +2431,9 @@ def get_internal_energy_from_profile(common_envelope_option_for_lambda, raise ValueError( "CEE problem calculating recombination (and H2 recombination) " "energy, remaining internal energy giving negative values.") + else: + raise ValueError("unsupported: common_envelope_option_for_lambda = {}"\ + .format(common_envelope_option_for_lambda)) return specific_donor_internal_energy @@ -2473,7 +2510,7 @@ def calculate_recombination_energy(profile, tolerance=0.001): frac_HeI = profile["neutral_fraction_He"] avg_charge_He = profile["avg_charge_He"] - for i in range(len(frac_HI)): + for i in range(len(frac_HeI)): frac_HeI[i] = min(1., frac_HeI[i]) # knowing the frac_HeI and the avg_charge_He, # we can solve for frac_HeII and frac_HeIII @@ -2647,9 +2684,11 @@ def calculate_Mejected_for_integrated_binding_energy(profile, Ebind_threshold, if donor_mass[ind_threshold]< mc1_i or donor_radius[ind_threshold] Date: Tue, 12 Nov 2024 09:56:41 +0100 Subject: [PATCH 30/34] update doc --- posydon/unit_tests/README.md | 27 ++++++++++++++++++++------- posydon/utils/common_functions.py | 1 - 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/posydon/unit_tests/README.md b/posydon/unit_tests/README.md index 7ce72f68a..ffbf0a387 100644 --- a/posydon/unit_tests/README.md +++ b/posydon/unit_tests/README.md @@ -42,7 +42,7 @@ There might be additional modules/functions needed to do the testing. *I recomme #### Test functions -For small single test you can simply define a single test function. The name should start with the prefix `test`. *I recommend to use the prefix `test_`.* The actual test is usually done by an assert statement. Usually, there will be several tests collected in a [class](#test-classes), thus single functions will be rare. +For a small single test you can simply define a single test function. The name should start with the prefix `test`. *I recommend to use the prefix `test_`.* The actual test is usually done by an assert statement. Usually, there will be several tests collected in a [class](#test-classes), thus single functions will be rare. #### Test classes @@ -58,7 +58,7 @@ Beside the existence and the type of a variable, we should verify the integrity ##### Check functions -Function in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions need variables, you may [use fixtures](#using-fixtures). +Functions in the module need checks according to their functionality. This should coincide with the doc-string of each function. *I suggest to have one class with all the function tests and a test function for each function in the module, which gets tested.* If the functions need variables, you may [use fixtures](#using-fixtures). Functions may include prints statements. To capture outputs to `stdout` and `stderr` pytest has global defined fixtures `capsys` (and `capsysbinary` for bytes data instead of usual text; addtionally, there are `capfd` and `capfdbinary` to capture the file descriptors `1` and `2`). @@ -87,7 +87,7 @@ will print the collected prints to stdout, at this moment all together (and clea ##### Check classes -Each class inside a module should be get its components checked like a module itself. *I suggest to have a test class for each class in the tested module and the test of each class function should get an own test function.* Again, [use fixtures](#using-fixtures) can be used to ensure that all tests run under the same conditions. +Each class inside a module should be get its components checked like a module itself. *I suggest to have a test class for each class in the tested module and the test of each class function should get an own test function.* Again, [fixtures](#using-fixtures) can be used to ensure that all tests run under the same conditions. A commonly useful fixture for a class test is an object of this class initialized with the defaults. #### Using fixtures @@ -95,17 +95,30 @@ You can define [fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html @pytest.fixture +*I suggest to import the function via `from pytest import fixture` and than call it via `@fixture`.* + Fixtures replace the `setUp` and `tearDown`. To use a fixture to prepare something before a test, you can simply write it as a function and the variable will contain the returned value. -To do with cleaning things up after a test, instead of having a final return, you separate setUp and tearDown with a yield statement, which ensure that all before is executed when the fixture is requested and the stuff after when it get deleted. For chains of fixtures it should be noted, that the clean up happens in the reverse order to the creation, because the innermost fixture will get deleted first. +For cleaning things up after a test, instead of having a final return, you separate setUp and tearDown with a `yield` statement, which ensure that all before is executed when the fixture is requested and the stuff after when it get deleted (usually at the end of the test function). For chains of fixtures it should be noted, that the clean up happens in the reverse order to the creation, because the innermost fixture will get deleted first. #### Catching raised errors -Pytest has the [context manager `pytest.raises`](https://docs.pytest.org/en/stable/reference/reference.html#pytest-raises) to catch raised errors. You use it like other context managers via a `with` statement. Beside the expected exception, you can specify a `match`, which will be checked against the error message. The context object can be used to check for more details of the raised error. +Pytest has the [context manager `pytest.raises`](https://docs.pytest.org/en/stable/reference/reference.html#pytest-raises) to catch raised errors. You use it like other context managers via a `with` statement. Beside the expected exception, you can specify a `match`, which will be checked against the error message as a regular expression, e.g.: + + with pytest.raises(TypeError, match="Error message"): + raise TypeError("Error message") + +The context object can be used to check for more details of the raised error (this is useful to overcome some limitations of escape characters in regular expressions). Here an example how to check the error message from the context object: + + with pytest.raises(TypeError) as error_info: + raise TypeError("Error message") + assert error_info.value.args[0] == "Error message" + +It should be noted that the error type is will match subclasses successfully. #### Catching warnings -Usually, pytest will catch all warnings and print them at the end of all tests. If your test will cause a warning which you don't like to have displayed, you can filter the warnings caught by pytest. To filter all warnings in a function or class you can decorate it with a filter, e.g. `@pytest.mark.filterwarnings("ignore:WARNINGTEXT")`. There are more things you can do on [warnings in pytest](https://docs.pytest.org/en/stable/how-to/capture-warnings.html), but you should use that only were needed. But you should be careful with the pytest warning catching, because it overwrites some parts of the python warnings, which even interferes badly with our POSYDON warnings (especially the filter changes). +Usually, pytest will catch all warnings and print them at the end of all tests. If your test will cause a warning which you don't like to have displayed, you can filter the warnings caught by pytest. To filter all warnings in a function or class you can decorate it with a filter, e.g. `@pytest.mark.filterwarnings("ignore:WARNINGTEXT")`. There are more things you can do on [warnings in pytest](https://docs.pytest.org/en/stable/how-to/capture-warnings.html), but you should use that only were needed. But you should be careful with the pytest warning catching, because it overwrites some parts of the python warnings, which even interferes badly with our POSYDON warnings (especially the filter changes). By using the `pytest.warns` context you can capture and check for warnings the same way as for [errors](#catching-raised-errors). ### Check that it can fail @@ -129,7 +142,7 @@ Strictly speaking it runs the `pytest` inside of [coverage](https://coverage.rea coverage run -m pytest TESTFILE -*I suggest to use the `--branch` option, which is `--cov-branch` in pytest.* The latter version to run the test has the advantage that you can make use of all the option provided by `coverage`, while running it through the plug-in will give you only access to the options implemented in there. On the other hand, running it with the plug-in will exclude the test code from coverage and give you the coverage report together with the report of the tests, while running it via `coverage` the report in the file `.coverage` needs to readout via +*I suggest to use the `--branch` option, which is `--cov-branch` in pytest.* The latter version to run the test has the advantage that you can make use of all the options provided by `coverage`, while running it through the plug-in will give you only access to the options implemented in there. On the other hand, running it with the plug-in will exclude the test code from coverage and give you the coverage report together with the report of the tests, while running it via `coverage` the report in the file `.coverage` needs to readout via coverage report diff --git a/posydon/utils/common_functions.py b/posydon/utils/common_functions.py index 8a51902e9..236c9f172 100644 --- a/posydon/utils/common_functions.py +++ b/posydon/utils/common_functions.py @@ -1934,7 +1934,6 @@ def calculate_core_boundary(donor_mass, both_conditions = ( element <= core_element_fraction_definition).__and__( element_core >= core_element_high_fraction_definition) - print(both_conditions) if not np.any(both_conditions): # the whole star is an envelope, from surface towards the core # the "both_conditions" never becomes True From 0e50bbb664e78633809644ebb356aace787385d2 Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 12 Nov 2024 10:43:13 +0100 Subject: [PATCH 31/34] merge development and update tests; include test in continuous_integration.yml --- .github/workflows/continuous_integration.yml | 6 +-- .../unit_tests/utils/test_common_functions.py | 39 +++++++++++---- .../utils/test_limits_thresholds.py | 11 +++++ posydon/utils/common_functions.py | 48 ++++++++++++------- 4 files changed, 73 insertions(+), 31 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 8c8604725..1729411b5 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -27,6 +27,6 @@ jobs: python -m pip install --upgrade pip pip install . - # - name: Run all tests - # run: | - # pytest posydon/unit_tests/ + - name: Run all tests + run: | + pytest posydon/unit_tests/ --cov=posydon.utils --cov-branch --cov-report term-missing diff --git a/posydon/unit_tests/utils/test_common_functions.py b/posydon/unit_tests/utils/test_common_functions.py index 313b77244..cb13a8569 100644 --- a/posydon/unit_tests/utils/test_common_functions.py +++ b/posydon/unit_tests/utils/test_common_functions.py @@ -334,7 +334,7 @@ def test_value_BURNING_STATES(self): assert v in totest.BURNING_STATES, "missing entry" def test_value_RICHNESS_STATES(self): - for v in ["H-rich", "stripped_He"]: #TODO: check add "accreted_He" + for v in ["H-rich", "stripped_He", "accreted_He"]: # check required values assert v in totest.RICHNESS_STATES, "missing entry" @@ -476,22 +476,39 @@ def test_rzams(self): assert totest.rzams(1.0, Zsun=1.0) ==\ approx(0.84963691291, abs=6e-12) - def test_roche_lobe_radius(self): + def test_roche_lobe_radius(self, capsys): # missing argument - with raises(TypeError, match="missing 1 required positional"+\ - " argument: 'q'"): + with raises(TypeError, match="missing 2 required positional"+\ + " arguments: 'm1' and 'm2'"): totest.roche_lobe_radius() + # bad input + for a in [-1.0, totest.np.array([]), totest.np.array([-1.0])]: + with warns(EvolutionWarning, match="Trying to compute RL radius"+\ + " for binary with invalid separation"): + assert totest.np.isnan(totest.roche_lobe_radius(1.0, 1.0,\ + a_orb=a)) + for m in [0.0, totest.np.array([]), totest.np.array([0.0])]: + with warns(EvolutionWarning, match="Trying to compute RL radius"+\ + " for nonexistent object"): + assert totest.np.isnan(totest.roche_lobe_radius(m, 1.0)) + for m in [0.0, totest.np.array([]), totest.np.array([0.0])]: + with warns(EvolutionWarning, match="Trying to compute RL radius"+\ + " for nonexistent companion"): + assert totest.np.isnan(totest.roche_lobe_radius(1.0, m)) # examples - assert totest.roche_lobe_radius(1.0) ==\ + assert totest.roche_lobe_radius(1.0, 1.0) ==\ approx(0.37892051838, abs=6e-12) - assert totest.roche_lobe_radius(2.0) ==\ + assert totest.roche_lobe_radius(2.0, 1.0) ==\ approx(0.44000423753, abs=6e-12) - assert totest.roche_lobe_radius(1.0, a_orb=2.0) ==\ + assert totest.roche_lobe_radius(1.0, 2.0) ==\ + approx(0.32078812033, abs=6e-12) + assert totest.roche_lobe_radius(1.0, 1.0, a_orb=2.0) ==\ approx(0.75784103676, abs=6e-12) + assert totest.np.allclose(totest.roche_lobe_radius(totest.np.array([1.0, 2.0, 1.0, 1.0]), totest.np.array([1.0, 1.0, 2.0, 1.0]), a_orb=totest.np.array([1.0, 1.0, 1.0, 2.0])), totest.np.array([0.37892051838, 0.44000423753, 0.32078812033, 0.75784103676])) # check that roche lobe sum never exceeds orbital separation - for q in [1.0e+1, 1.0e+2, 1.0e+3, 1.0e+4, 1.0e+5, 1.0e+6, 1.0e+7]: - assert totest.roche_lobe_radius(q)+totest.roche_lobe_radius(1.0/q)\ - < 1.0 + for m in [1.0e+1, 1.0e+2, 1.0e+3, 1.0e+4, 1.0e+5, 1.0e+6, 1.0e+7]: + assert totest.roche_lobe_radius(m, 1.0)+\ + totest.roche_lobe_radius(1.0, m) < 1.0 def test_orbital_separation_from_period(self): # missing argument @@ -1169,6 +1186,8 @@ def test_infer_star_state(self): for lgLHe in [2.0*LBT, LBT, 0.5*LBT]: if sH1>THNA: rich = "H-rich" + elif sH1= 0,\ + "MIN_COUNT_INITIAL_RLO_BOUNDARY should be 0 or larger" + def test_limits_THRESHOLD_CENTRAL_ABUNDANCE(self): # an abundance should be in [0,1] assert totest.THRESHOLD_CENTRAL_ABUNDANCE >= 0.0,\ diff --git a/posydon/utils/common_functions.py b/posydon/utils/common_functions.py index 25207c573..5aaf5a767 100644 --- a/posydon/utils/common_functions.py +++ b/posydon/utils/common_functions.py @@ -211,43 +211,54 @@ def roche_lobe_radius(m1, m2, a_orb=1): .. [1] Eggleton, P. P. 1983, ApJ, 268, 368 """ - ## catching if a_orb is an empty array or is an array with invalid separation values + ## catching if a_orb is an empty array or is an array with invalid + ## separation values if isinstance(a_orb, np.ndarray): ## if array is empty, fill with NaN values if a_orb.size == 0: - Pwarn("Trying to compute RL radius for binary with invalid separation", "EvolutionWarning") - a_orb = np.full([1 if s==0 else s for s in a_orb.shape], np.nan, dtype=np.float64) + Pwarn("Trying to compute RL radius for binary with invalid" + " separation", "EvolutionWarning") + a_orb = np.full([1 if s==0 else s for s in a_orb.shape], np.nan, + dtype=np.float64) ## if array contains invalid values, replace with NaN elif np.any(a_orb < 0): - Pwarn("Trying to compute RL radius for binary with invalid separation", "EvolutionWarning") + Pwarn("Trying to compute RL radius for binary with invalid" + " separation", "EvolutionWarning") a_orb[a_orb < 0] = np.nan ## catching if a_orb is a float with invalid separation value elif a_orb < 0: - Pwarn("Trying to compute RL radius for binary with invalid separation", "EvolutionWarning") + Pwarn("Trying to compute RL radius for binary with invalid separation", + "EvolutionWarning") a_orb = np.nan - if isinstance(m1, np.ndarray): if m1.size == 0: - Pwarn("Trying to compute RL radius for nonexistent object", "EvolutionWarning") - m1 = np.full([1 if s==0 else s for s in m1.shape], np.nan, dtype=np.float64) + Pwarn("Trying to compute RL radius for nonexistent object", + "EvolutionWarning") + m1 = np.full([1 if s==0 else s for s in m1.shape], np.nan, + dtype=np.float64) elif np.any(m1 <= 0): - Pwarn("Trying to compute RL radius for nonexistent object", "EvolutionWarning") + Pwarn("Trying to compute RL radius for nonexistent object", + "EvolutionWarning") m1[m1 <= 0] = np.nan - elif m1 <=0: - Pwarn("Trying to compute RL radius for nonexistent object", "EvolutionWarning") + elif m1 <= 0: + Pwarn("Trying to compute RL radius for nonexistent object", + "EvolutionWarning") m1 = np.nan - if isinstance(m2, np.ndarray): if m2.size == 0: - Pwarn("Trying to compute RL radius for nonexistent companion", "EvolutionWarning") - m2 = np.full([1 if s==0 else s for s in m2.shape], np.nan, dtype=np.float64) + Pwarn("Trying to compute RL radius for nonexistent companion", + "EvolutionWarning") + m2 = np.full([1 if s==0 else s for s in m2.shape], np.nan, + dtype=np.float64) elif np.any(m2 <= 0): - Pwarn("Trying to compute RL radius for nonexistent companion", "EvolutionWarning") + Pwarn("Trying to compute RL radius for nonexistent companion", + "EvolutionWarning") m2[m2 <= 0] = np.nan - elif m2 <=0: - Pwarn("Trying to compute RL radius for nonexistent companion", "EvolutionWarning") + elif m2 <= 0: + Pwarn("Trying to compute RL radius for nonexistent companion", + "EvolutionWarning") m2 = np.nan q = m1/m2 @@ -1344,7 +1355,8 @@ def infer_star_state(star_mass=None, surface_h1=None, return STATE_UNDETERMINED rich_in = ("H-rich" if surface_h1 > THRESHOLD_HE_NAKED_ABUNDANCE - else ("accreted_He" if round(surface_h1, 10) LOG10_BURNING_THRESHOLD and log_LH - log_Lnuc > REL_LOG10_BURNING_THRESHOLD) burning_He = (log_LHe > LOG10_BURNING_THRESHOLD From 8552872fb3098a74545b64afb5b1c6b005a8735d Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 12 Nov 2024 10:47:05 +0100 Subject: [PATCH 32/34] add pytest/cov install to continuous_integration.yml --- .github/workflows/continuous_integration.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 1729411b5..f19374c76 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -25,8 +25,10 @@ jobs: - name: Install POSYDON without extras run: | python -m pip install --upgrade pip - pip install . + pip install . - name: Run all tests run: | + pip install pytest + pip install pytest-cov pytest posydon/unit_tests/ --cov=posydon.utils --cov-branch --cov-report term-missing From ac680086d2a19f9266db60bc6e4dc6a1554b106c Mon Sep 17 00:00:00 2001 From: mkruckow Date: Tue, 12 Nov 2024 10:52:42 +0100 Subject: [PATCH 33/34] add posydon install to unit test run --- .github/workflows/continuous_integration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index f19374c76..2ff2223e1 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -29,6 +29,8 @@ jobs: - name: Run all tests run: | + python -m pip install --upgrade pip + pip install . pip install pytest pip install pytest-cov pytest posydon/unit_tests/ --cov=posydon.utils --cov-branch --cov-report term-missing From 27874a54e1dfb507a212f85e75017d0856b53cf6 Mon Sep 17 00:00:00 2001 From: Max Briel Date: Tue, 12 Nov 2024 14:12:16 +0100 Subject: [PATCH 34/34] change to local python version --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 2ff2223e1..8abf6b76b 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -33,4 +33,4 @@ jobs: pip install . pip install pytest pip install pytest-cov - pytest posydon/unit_tests/ --cov=posydon.utils --cov-branch --cov-report term-missing + python -m pytest posydon/unit_tests/ --cov=posydon.utils --cov-branch --cov-report term-missing