Skip to content

Commit

Permalink
Ensuring we throw an error on bad YAML files (#1358)
Browse files Browse the repository at this point in the history
The goal is to throw an error when there is a duplicate
key in the YAML file. Though if the key is heavily
nested, deep in the YAML file, sometimes our YAML
libraries will miss it.
  • Loading branch information
john-science authored Aug 22, 2023
1 parent 48f5438 commit afabda9
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 2 deletions.
1 change: 1 addition & 0 deletions armi/nucDirectory/nuclideBases.py
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,7 @@ def imposeBurnChain(burnChainStream):
return
burnChainImposed = True
yaml = YAML(typ="rt")
yaml.allow_duplicate_keys = False
burnData = yaml.load(burnChainStream)

for nucName, burnInfo in burnData.items():
Expand Down
19 changes: 19 additions & 0 deletions armi/reactor/blueprints/tests/test_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,25 @@ class TestBlueprintsSchema(unittest.TestCase):
2 2 2 2 2
"""

def test_noDuplicateKeysInYamlBlueprints(self):
"""
Prove that if you duplicate a section of a YAML blueprint file,
a hard error will be thrown.
"""
# loop through a few different sections, to test blueprints broadly
sections = ["blocks:", "components:", "component groups:"]
for sectionName in sections:
# modify blueprint YAML to duplicate this section
yamlString = str(self._yamlString)
i = yamlString.find(sectionName)
lenSection = yamlString[i:].find("\n\n")
section = yamlString[i : i + lenSection]
yamlString = yamlString[:i] + section + yamlString[i : i + lenSection]

# validate that this is now an invalid YAML blueprint
with self.assertRaises(Exception):
_design = blueprints.Blueprints.load(yamlString)

def test_assemblyParameters(self):
cs = settings.Settings()
design = blueprints.Blueprints.load(self._yamlString)
Expand Down
1 change: 1 addition & 0 deletions armi/reactor/systemLayoutInput.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ def _readYaml(self, stream):
consistent inputs.
"""
yaml = YAML()
yaml.allow_duplicate_keys = False
tree = yaml.load(stream)
tree = INPUT_SCHEMA(tree)
self.assemTypeByIndices.clear()
Expand Down
5 changes: 3 additions & 2 deletions armi/settings/caseSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

r"""
"""
This defines a Settings object that acts mostly like a dictionary. It
is meant to be treated mostly like a singleton, where each custom ARMI
object has access to it. It contains global user settings like the core
Expand All @@ -22,7 +22,7 @@
A settings object can be saved as or loaded from an YAML file. The ARMI GUI is designed to
create this settings file, which is then loaded by an ARMI process on the cluster.
A primary case settings is created as ``masterCs``
A primary case settings is created as ``masterCs``.
"""
import io
import logging
Expand Down Expand Up @@ -401,6 +401,7 @@ def getSettingsSetByUser(self, fPath):
# from the settings file to know which settings are user-defined
with open(fPath, "r") as stream:
yaml = YAML()
yaml.allow_duplicate_keys = False
tree = yaml.load(stream)
userSettings = tree[settingsIO.Roots.CUSTOM]

Expand Down
1 change: 1 addition & 0 deletions armi/settings/settingsIO.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def _readYaml(self, stream):
from armi.settings.fwSettings.globalSettings import CONF_VERSIONS

yaml = YAML(typ="rt")
yaml.allow_duplicate_keys = False
tree = yaml.load(stream)
if "settings" not in tree:
raise InvalidSettingsFileError(
Expand Down
15 changes: 15 additions & 0 deletions armi/settings/tests/test_settingsIO.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from armi.cli import entryPoint
from armi.settings import setting
from armi.settings import settingsIO
from armi.tests import TEST_ROOT
from armi.utils import directoryChangers
from armi.utils.customExceptions import (
InvalidSettingsFileError,
Expand Down Expand Up @@ -68,6 +69,20 @@ def test_basicSettingsReader(self):
self.assertFalse(getattr(reader, "filelessBP"))
self.assertEqual(getattr(reader, "path"), "")

def test_readFromFile(self):
with directoryChangers.TemporaryDirectoryChanger():
inPath = os.path.join(TEST_ROOT, "armiRun.yaml")
outPath = "test_readFromFile.yaml"

txt = open(inPath, "r").read()
verb = "branchVerbosity:"
txt0, txt1 = txt.split(verb)
newTxt = f"{txt0}{verb} fake\n {verb}{txt1}"
open(outPath, "w").write(newTxt)

with self.assertRaises(InvalidSettingsFileError):
settings.caseSettings.Settings(outPath)


class SettingsRenameTests(unittest.TestCase):
testSettings = [
Expand Down

0 comments on commit afabda9

Please sign in to comment.