From 2bbd3002ad607d1bf73a5fd567abab91371177c9 Mon Sep 17 00:00:00 2001 From: John Stilley <1831479+john-science@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:52:28 -0800 Subject: [PATCH] Adding tests and test crumbs for settings and bookkeeping (#1542) --- armi/bookkeeping/db/database3.py | 33 ++++++---- .../db/tests/test_databaseInterface.py | 48 +++++++++++++-- armi/bookkeeping/historyTracker.py | 5 +- armi/bookkeeping/tests/test_historyTracker.py | 61 ++++++++++++++++++- armi/operators/tests/test_operators.py | 6 ++ armi/reactor/tests/test_reactors.py | 2 +- armi/settings/tests/test_settings.py | 27 ++++++++ 7 files changed, 158 insertions(+), 24 deletions(-) diff --git a/armi/bookkeeping/db/database3.py b/armi/bookkeeping/db/database3.py index 3b9a30767..189896a21 100644 --- a/armi/bookkeeping/db/database3.py +++ b/armi/bookkeeping/db/database3.py @@ -201,23 +201,14 @@ def open(self): "Cannot open database with permission `{}`".format(self._permission) ) + # open the database, and write a bunch of metadata to it runLog.info("Opening database file at {}".format(os.path.abspath(filePath))) self.h5db = h5py.File(filePath, self._permission) self.h5db.attrs["successfulCompletion"] = False self.h5db.attrs["version"] = meta.__version__ self.h5db.attrs["databaseVersion"] = self.version - self.h5db.attrs["user"] = context.USER - self.h5db.attrs["python"] = sys.version - self.h5db.attrs["armiLocation"] = os.path.dirname(context.ROOT) - self.h5db.attrs["startTime"] = context.START_TIME - self.h5db.attrs["machines"] = numpy.array(context.MPI_NODENAMES).astype("S") - # store platform data - platform_data = uname() - self.h5db.attrs["platform"] = platform_data.system - self.h5db.attrs["hostname"] = platform_data.node - self.h5db.attrs["platformRelease"] = platform_data.release - self.h5db.attrs["platformVersion"] = platform_data.version - self.h5db.attrs["platformArch"] = platform_data.processor + self.writeSystemAttributes(self.h5db) + # store app and plugin data app = getApp() self.h5db.attrs["appName"] = app.name @@ -228,9 +219,25 @@ def open(self): ] ps = numpy.array([str(p[0]) + ":" + str(p[1]) for p in ps]).astype("S") self.h5db.attrs["pluginPaths"] = ps - # store the commit hash of the local repo self.h5db.attrs["localCommitHash"] = Database3.grabLocalCommitHash() + @staticmethod + def writeSystemAttributes(h5db): + """Write system attributes to the database.""" + h5db.attrs["user"] = context.USER + h5db.attrs["python"] = sys.version + h5db.attrs["armiLocation"] = os.path.dirname(context.ROOT) + h5db.attrs["startTime"] = context.START_TIME + h5db.attrs["machines"] = numpy.array(context.MPI_NODENAMES).astype("S") + + # store platform data + platform_data = uname() + h5db.attrs["platform"] = platform_data.system + h5db.attrs["hostname"] = platform_data.node + h5db.attrs["platformRelease"] = platform_data.release + h5db.attrs["platformVersion"] = platform_data.version + h5db.attrs["platformArch"] = platform_data.processor + @staticmethod def grabLocalCommitHash(): """ diff --git a/armi/bookkeeping/db/tests/test_databaseInterface.py b/armi/bookkeeping/db/tests/test_databaseInterface.py index 860d32362..17ae0ecdb 100644 --- a/armi/bookkeeping/db/tests/test_databaseInterface.py +++ b/armi/bookkeeping/db/tests/test_databaseInterface.py @@ -135,13 +135,41 @@ def setUp(self): def tearDown(self): self.td.__exit__(None, None, None) + def test_writeSystemAttributes(self): + """Test the writeSystemAttributes method. + + .. test:: Validate that we can directly write system attributes to a database file. + :id: T_ARMI_DB_QA0 + :tests: R_ARMI_DB_QA + """ + with h5py.File("test_writeSystemAttributes.h5", "w") as h5: + Database3.writeSystemAttributes(h5) + + with h5py.File("test_writeSystemAttributes.h5", "r") as h5: + self.assertIn("user", h5.attrs) + self.assertIn("python", h5.attrs) + self.assertIn("armiLocation", h5.attrs) + self.assertIn("startTime", h5.attrs) + self.assertIn("machines", h5.attrs) + self.assertIn("platform", h5.attrs) + self.assertIn("hostname", h5.attrs) + self.assertIn("platformRelease", h5.attrs) + self.assertIn("platformVersion", h5.attrs) + self.assertIn("platformArch", h5.attrs) + def test_metaData_endSuccessfully(self): - def goodMethod(cycle, node): - pass + """Test databases have the correct metadata in them. + .. test:: Validate that databases have system attributes written to them during the usual workflow. + :id: T_ARMI_DB_QA1 + :tests: R_ARMI_DB_QA + """ # the power should start at zero self.assertEqual(self.r.core.p.power, 0) + def goodMethod(cycle, node): + pass + self.o.interfaces.append(MockInterface(self.o.r, self.o.cs, goodMethod)) with self.o: self.o.operate() @@ -152,15 +180,23 @@ def goodMethod(cycle, node): with h5py.File(self.o.cs.caseTitle + ".h5", "r") as h5: self.assertTrue(h5.attrs["successfulCompletion"]) self.assertEqual(h5.attrs["version"], version) + + self.assertIn("caseTitle", h5.attrs) + self.assertIn("geomFile", h5["inputs"]) + self.assertIn("settings", h5["inputs"]) + self.assertIn("blueprints", h5["inputs"]) + + # validate system attributes self.assertIn("user", h5.attrs) self.assertIn("python", h5.attrs) self.assertIn("armiLocation", h5.attrs) self.assertIn("startTime", h5.attrs) self.assertIn("machines", h5.attrs) - self.assertIn("caseTitle", h5.attrs) - self.assertIn("geomFile", h5["inputs"]) - self.assertIn("settings", h5["inputs"]) - self.assertIn("blueprints", h5["inputs"]) + self.assertIn("platform", h5.attrs) + self.assertIn("hostname", h5.attrs) + self.assertIn("platformRelease", h5.attrs) + self.assertIn("platformVersion", h5.attrs) + self.assertIn("platformArch", h5.attrs) # after operating, the power will be greater than zero self.assertGreater(self.r.core.p.power, 1e9) diff --git a/armi/bookkeeping/historyTracker.py b/armi/bookkeeping/historyTracker.py index 766b1e444..ff9193990 100644 --- a/armi/bookkeeping/historyTracker.py +++ b/armi/bookkeeping/historyTracker.py @@ -95,6 +95,10 @@ class HistoryTrackerInterface(interfaces.Interface): """ Makes reports of the state that individual assemblies encounter. + .. impl:: This interface allows users to retrieve run data from somewhere other than the database. + :id: I_ARMI_HIST_TRACK + :implements: R_ARMI_HIST_TRACK + Attributes ---------- detailAssemblyNames : list @@ -102,7 +106,6 @@ class HistoryTrackerInterface(interfaces.Interface): time : list list of reactor time in years - """ name = "history" diff --git a/armi/bookkeeping/tests/test_historyTracker.py b/armi/bookkeeping/tests/test_historyTracker.py index 8a2bbb0a2..a75beb943 100644 --- a/armi/bookkeeping/tests/test_historyTracker.py +++ b/armi/bookkeeping/tests/test_historyTracker.py @@ -107,6 +107,10 @@ def test_calcMGFluence(self): armi.bookeeping.db.hdf.hdfDB.readBlocksHistory requires historical_values[historical_indices] to be cast as a list to read more than the first energy group. This test shows that this behavior is preserved. + + .. test:: Demonstrate that a parameter stored at differing time nodes can be recovered. + :id: T_ARMI_HIST_TRACK0 + :tests: R_ARMI_HIST_TRACK """ o = self.o b = o.r.core.childrenByLocator[o.r.core.spatialGrid[0, 0, 0]].getFirstBlock( @@ -115,9 +119,8 @@ def test_calcMGFluence(self): bVolume = b.getVolume() bName = b.name - hti = o.getInterface("history") - # duration is None in this DB + hti = o.getInterface("history") timesInYears = [duration or 1.0 for duration in hti.getTimeSteps()] timeStepsToRead = [ utils.getCycleNodeFromCumulativeNode(i, self.o.cs) @@ -128,7 +131,7 @@ def test_calcMGFluence(self): mgFluence = None for ts, years in enumerate(timesInYears): cycle, node = utils.getCycleNodeFromCumulativeNode(ts, self.o.cs) - # b.p.mgFlux is vol integrated + # b.p.mgFlux is vol integrated mgFlux = hti.getBlockHistoryVal(bName, "mgFlux", (cycle, node)) / bVolume timeInSec = years * 365 * 24 * 3600 if mgFluence is None: @@ -143,6 +146,58 @@ def test_calcMGFluence(self): hti.unloadBlockHistoryVals() self.assertIsNone(hti._preloadedBlockHistory) + def test_historyParameters(self): + """Retrieve various paramaters from the history. + + .. test:: Demonstrate that various parameters stored at differing time nodes can be recovered. + :id: T_ARMI_HIST_TRACK1 + :tests: R_ARMI_HIST_TRACK + """ + o = self.o + b = o.r.core.childrenByLocator[o.r.core.spatialGrid[0, 0, 0]].getFirstBlock( + Flags.FUEL + ) + b.getVolume() + bName = b.name + + # duration is None in this DB + hti = o.getInterface("history") + timesInYears = [duration or 1.0 for duration in hti.getTimeSteps()] + timeStepsToRead = [ + utils.getCycleNodeFromCumulativeNode(i, self.o.cs) + for i in range(len(timesInYears)) + ] + hti.preloadBlockHistoryVals([bName], ["power"], timeStepsToRead) + + # read some parameters + params = {} + for param in ["height", "pdens", "power"]: + params[param] = [] + for ts, years in enumerate(timesInYears): + cycle, node = utils.getCycleNodeFromCumulativeNode(ts, self.o.cs) + + params[param].append( + hti.getBlockHistoryVal(bName, param, (cycle, node)) + ) + + # verify the height parameter doesn't change over time + self.assertGreater(params["height"][0], 0) + self.assertEqual(params["height"][0], params["height"][1]) + + # verify the power parameter is retrievable from the history + self.assertEqual(o.cs["power"], 1000000000.0) + self.assertAlmostEqual(params["power"][0], 360, delta=0.1) + self.assertEqual(params["power"][0], params["power"][1]) + + # verify the power density parameter is retrievable from the history + self.assertAlmostEqual(params["pdens"][0], 0.0785, delta=0.001) + self.assertEqual(params["pdens"][0], params["pdens"][1]) + + # test that unloadBlockHistoryVals() is working + self.assertIsNotNone(hti._preloadedBlockHistory) + hti.unloadBlockHistoryVals() + self.assertIsNone(hti._preloadedBlockHistory) + def test_historyReport(self): """ Test generation of history report. diff --git a/armi/operators/tests/test_operators.py b/armi/operators/tests/test_operators.py index e389e8955..0f17ea0aa 100644 --- a/armi/operators/tests/test_operators.py +++ b/armi/operators/tests/test_operators.py @@ -438,6 +438,12 @@ def setUp(self): self.detailedOperator = Operator(self.standaloneDetailedCS) def test_getPowerFractions(self): + """Test that the power fractions are calculated correctly. + + .. test:: Test the powerFractions are retrieved correctly for multiple cycles. + :id: T_ARMI_SETTINGS_POWER1 + :tests: R_ARMI_SETTINGS_POWER + """ self.assertEqual( self.detailedOperator.powerFractions, self.powerFractionsSolution ) diff --git a/armi/reactor/tests/test_reactors.py b/armi/reactor/tests/test_reactors.py index d6f9960bd..6560ea20f 100644 --- a/armi/reactor/tests/test_reactors.py +++ b/armi/reactor/tests/test_reactors.py @@ -293,7 +293,7 @@ def test_getSetParameters(self): :tests: R_ARMI_PARAM_PART .. test:: Ensure there is a setting for total core power. - :id: T_ARMI_SETTINGS_POWER + :id: T_ARMI_SETTINGS_POWER0 :tests: R_ARMI_SETTINGS_POWER """ # Test at reactor level diff --git a/armi/settings/tests/test_settings.py b/armi/settings/tests/test_settings.py index bb90a5277..1d62ae894 100644 --- a/armi/settings/tests/test_settings.py +++ b/armi/settings/tests/test_settings.py @@ -107,6 +107,33 @@ def test_updateEnvironmentSettingsFrom(self): self.cs.updateEnvironmentSettingsFrom(newEnv) self.assertEqual(self.cs["verbosity"], "9") + def test_metaData(self): + """Test we can get and set the important settings metadata. + + .. test:: Test getting and setting import settings metadata. + :id: T_ARMI_SETTINGS_META + :tests: R_ARMI_SETTINGS_META + """ + # test get/set on caseTitle + self.assertEqual(self.cs.caseTitle, "armi") + testTitle = "test_metaData" + self.cs.caseTitle = testTitle + self.assertEqual(self.cs.caseTitle, testTitle) + + # test get/set on comment + self.assertEqual(self.cs["comment"], "") + testComment = "Comment: test_metaData" + self.cs = self.cs.modified(newSettings={"comment": testComment}) + self.assertEqual(self.cs["comment"], testComment) + + # test get/set on version + self.assertEqual(len(self.cs["versions"]), 0) + self.cs = self.cs.modified(newSettings={"versions": {"something": 1.234}}) + + d = self.cs["versions"] + self.assertEqual(len(d), 1) + self.assertEqual(d["something"], 1.234) + class TestAddingOptions(unittest.TestCase): def setUp(self):