blob: e67a09a3153bfe03e8ac2a291794338f0a703a6a [file]
############################################################################
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
import json
import tempfile
from pathlib import Path
import pytest
import yaml
from ntfc.cli.main import (
load_config_files,
update_nested_dict,
)
class MockEnvironment:
"""Mock Click environment for testing."""
def __init__(self, confpath: str = ""):
self.confpath = confpath
self.loops = 1
self.verbose = False
self.jsonconf = None
self.rebuild = False
self.flash = False
def test_load_config_files_single_file():
"""Test loading a single YAML file."""
yaml_file = "./tests/resources/nuttx/sim/config.yaml"
ctx = MockEnvironment(yaml_file)
conf, conf_json = load_config_files(ctx)
assert conf is not None
assert "config" in conf
assert "product" in conf
assert conf["config"]["loops"] == 1
def test_load_config_files_directory():
"""Test loading and merging YAML files from a directory."""
config_dir = "./tests/resources/yaml_configs"
ctx = MockEnvironment(config_dir)
conf, conf_json = load_config_files(ctx)
assert conf is not None
assert "config" in conf
assert "product" in conf
# Check files were merged in alphabetical order
assert conf["config"]["name"] == "base_config"
# Overridden in 03-overrides.yaml
assert conf["config"]["debug"] is True
# Added in 02-multicore.yaml
assert conf["product"]["cores"]["core1"]["name"] == "cpu1"
def test_load_config_files_directory_with_invalid_yaml():
"""Test that invalid YAML files are skipped gracefully."""
with tempfile.TemporaryDirectory() as tmpdir:
# Create valid file
valid_yaml = Path(tmpdir) / "01-valid.yaml"
with open(valid_yaml, "w") as f:
yaml.safe_dump(
{
"config": {"valid": True},
"product": {
"name": "test",
"cores": {"core0": {"name": "main"}},
},
},
f,
)
# Create invalid file that will be skipped
invalid_yaml = Path(tmpdir) / "02-invalid.yaml"
with open(invalid_yaml, "w") as f:
f.write("invalid: yaml: [")
ctx = MockEnvironment(tmpdir)
conf, conf_json = load_config_files(ctx)
# Valid files should be loaded, invalid skipped
assert conf["config"]["valid"] is True
def test_update_nested_dict():
"""Test merging of nested dictionaries."""
dict1 = {
"config": {"debug": False, "timeout": 30},
"level1": {"level2": {"value": "old", "keep": "preserved"}},
}
dict2 = {
"config": {"debug": True, "verbose": True},
"level1": {"level2": {"value": "new"}, "new_key": "added"},
}
result = update_nested_dict(dict1, dict2)
# Original values preserved if not overridden
assert result["config"]["timeout"] == 30
# Overridden values
assert result["config"]["debug"] is True
# New values added
assert result["config"]["verbose"] is True
# Deep nesting preserved and merged
assert result["level1"]["level2"]["value"] == "new"
assert result["level1"]["level2"]["keep"] == "preserved"
assert result["level1"]["new_key"] == "added"
def test_load_config_files_empty_directory():
"""Test loading from directory with no valid YAML files."""
with tempfile.TemporaryDirectory() as tmpdir:
ctx = MockEnvironment(tmpdir)
with pytest.raises(IOError):
load_config_files(ctx)
def test_load_config_files_json_args_override_and_add():
"""JSON session args override YAML config values and add new ones."""
yaml_file = "./tests/resources/nuttx/sim/config.yaml"
with tempfile.NamedTemporaryFile(
mode="w", suffix=".json", delete=False
) as tmpjson:
json.dump(
{
"module": {},
"args": {
"timeout": 321,
"new_option": "from_json",
"loops": 7,
"kv": {"CONFIG_FOO": "y"},
},
},
tmpjson,
)
json_path = tmpjson.name
try:
ctx = MockEnvironment(yaml_file)
ctx.jsonconf = json_path
conf, conf_json = load_config_files(ctx)
assert conf_json["args"]["timeout"] == 321
assert conf["config"]["timeout"] == 321
assert conf["config"]["new_option"] == "from_json"
assert conf["config"]["loops"] == 7
assert conf["config"]["kv"] == {"CONFIG_FOO": "y"}
finally:
Path(json_path).unlink()
def test_load_config_files_json_args_ignored_if_not_object():
"""Non-object JSON args does not modify YAML config."""
yaml_file = "./tests/resources/nuttx/sim/config.yaml"
with tempfile.NamedTemporaryFile(
mode="w", suffix=".json", delete=False
) as tmpjson:
json.dump({"args": ["timeout", 100]}, tmpjson)
json_path = tmpjson.name
try:
ctx = MockEnvironment(yaml_file)
ctx.jsonconf = json_path
conf, _conf_json = load_config_files(ctx)
assert conf["config"]["loops"] == 1
assert "timeout" not in conf["config"]
finally:
Path(json_path).unlink()