ATLAS Offline Software
Toggle main menu visibility
Loading...
Searching...
No Matches
PhysicsAnalysis
Algorithms
AnalysisAlgorithmsConfig
python
ConfigText_merge_unitTest.py
Go to the documentation of this file.
1
#!/usr/bin/env python3
2
# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3
#
4
# @author Nikita Pond
5
"""
6
Merge tests for multi-include semantics (no pytest), using neutral keys foo/bar.
7
8
Usage:
9
python ConfigText_merge_unitTest.py --dir AnalysisAlgorithmsConfig
10
python ConfigText_merge_unitTest.py --case all --dir path/to/yamls
11
"""
12
13
import
argparse
14
import
os
15
import
pathlib
16
import
sys
17
import
traceback
18
import
warnings
19
from
AnalysisAlgorithmsConfig.ConfigText
import
combineConfigFiles, TextConfigWarning
20
import
yaml
21
22
def
_load_yaml
(p: pathlib.Path):
23
with
p.open(
"r"
)
as
f:
24
return
yaml.safe_load(f)
25
26
def
_assert
(cond, msg):
27
if
not
cond:
28
raise
AssertionError(msg)
29
30
def
_run_merge
(top_cfg: dict, base_dir: pathlib.Path):
31
# Round-trip copy for parity with production behavior
32
top = yaml.safe_load(yaml.safe_dump(top_cfg))
33
changed = combineConfigFiles(top, base_dir, fragment_key=
"include"
)
34
_assert
(changed
is
True
,
"combineConfigFiles reported no changes"
)
35
_assert
(
"include"
not
in
top,
"include key should be removed after merging"
)
36
return
top
37
38
def
case_earlier_wins
(base_dir: pathlib.Path):
39
"""
40
Nested include: earlier wins inside test_merge_mid, local overrides afterward.
41
"""
42
top_cfg =
_load_yaml
(base_dir /
"test_merge_top.yaml"
)
43
merged =
_run_merge
(top_cfg, base_dir)
44
45
# From override.json (only there), should be present
46
_assert
(merged[
"a"
] == 2,
"Expected 'a' == 2 from override.json"
)
47
48
# From override.json (new key) and local override
49
_assert
(merged[
"nested"
][
"y"
] == 20,
"Expected nested.y == 20 from override.json"
)
50
_assert
(merged[
"nested"
][
"x"
] == 999,
"Local override of nested.x should win"
)
51
52
# Carry-through from frag_a (since mid includes frag_a first and no frag_b)
53
_assert
(abs(merged[
"bar"
][
"rate"
] - 0.1) < 1e-12,
"Expected bar.rate from frag_a (0.1)"
)
54
_assert
(merged[
"foo"
][
"alpha"
] == 1
and
merged[
"foo"
][
"beta"
] ==
"A"
,
55
"Expected foo.{alpha,beta} from frag_a"
)
56
57
# Local override over fragments
58
_assert
(merged[
"bar"
][
"size"
] == 32,
"Local bar.size should override frag_a (32 vs 64)"
)
59
60
# Presence of local-only key
61
_assert
(merged[
"local_only"
]
is
True
,
"Local-only key should remain"
)
62
63
def
case_earlier_wins_between_fragments
(base_dir: pathlib.Path):
64
"""
65
Direct include [frag_a, frag_b]: earlier (a) wins on scalar conflicts; lists concat.
66
"""
67
top_cfg = {
68
"include"
: [
"test_merge_frag_a.yaml"
,
"test_merge_frag_b.yaml"
]
69
}
70
merged =
_run_merge
(top_cfg, base_dir)
71
72
# Earlier wins on conflicts
73
_assert
(merged[
"foo"
][
"alpha"
] == 1,
"Earlier frag_a should win foo.alpha"
)
74
_assert
(merged[
"foo"
][
"beta"
] ==
"A"
,
"Earlier frag_a should win foo.beta"
)
75
_assert
(abs(merged[
"bar"
][
"rate"
] - 0.1) < 1e-12,
"Earlier frag_a should win bar.rate"
)
76
77
# Values only in earlier remain
78
_assert
(merged[
"bar"
][
"size"
] == 64,
"bar.size should come from frag_a"
)
79
80
# Lists concatenate
81
_assert
(merged[
"nums"
] == [1, 2, 3],
"Expected nums concatenation [1,2,3]"
)
82
83
# Keys unique to later survive
84
_assert
(merged[
"only_in_b"
]
is
True
,
"Key unique to later fragment should be present"
)
85
86
def
case_list_concat
(base_dir: pathlib.Path):
87
"""Lists concatenate without deduplication."""
88
top_cfg =
_load_yaml
(base_dir /
"test_merge_list_top.yaml"
)
89
merged =
_run_merge
(top_cfg, base_dir)
90
_assert
(merged[
"items"
] == [1, 2, 3],
"items should concatenate"
)
91
_assert
(merged[
"dups"
] == [1, 1, 1],
"dups should concatenate without dedup"
)
92
93
def
case_single_string_include
(base_dir: pathlib.Path):
94
"""Single-string include path is supported, but should warn for deprecation."""
95
top_cfg =
_load_yaml
(base_dir /
"test_merge_single_top.yaml"
)
96
97
# Capture warnings of the specific type
98
with
warnings.catch_warnings(record=
True
)
as
w:
99
warnings.simplefilter(
"always"
, TextConfigWarning)
# ensure it’s recorded
100
101
merged =
_run_merge
(top_cfg, base_dir)
102
103
# Pull only the TextConfigWarning messages
104
msgs = [
105
str(rec.message)
for
rec
in
w
106
if
issubclass(rec.category, TextConfigWarning)
107
]
108
109
# Assert that the expected deprecation message appeared
110
_assert
(
111
any(
"should be followed with a list of files"
in
m
for
m
in
msgs),
112
"Expected TextConfigWarning about include needing a list"
,
113
)
114
115
# Keep your existing assertions
116
_assert
(merged[
"foo"
][
"alpha"
] == 1,
"Single include should resolve frag_a (foo.alpha)"
)
117
_assert
(merged[
"only_in_a"
]
is
True
,
"Value from frag_a should appear"
)
118
119
def
run_case
(name: str, base_dir: pathlib.Path):
120
mapping = {
121
"earlier_wins"
: case_earlier_wins,
122
"earlier_wins_between_fragments"
: case_earlier_wins_between_fragments,
123
"list_concat"
: case_list_concat,
124
"single_string_include"
: case_single_string_include,
125
}
126
if
name ==
"all"
:
127
for
k
in
mapping:
128
print
(f
"→ {k} ... "
, end=
""
, flush=
True
)
129
try
:
130
mapping[k](base_dir)
131
print
(
"OK"
)
132
except
Exception:
133
print
(
"FAIL"
)
134
traceback.print_exc()
135
sys.exit(1)
136
else
:
137
if
name
not
in
mapping:
138
sys.exit(f
"Unknown case '{name}'. Choose from: all, "
+
", "
.join(mapping))
139
print
(f
"→ {name} ... "
, end=
""
, flush=
True
)
140
mapping[name](base_dir)
141
print
(
"OK"
)
142
143
def
main
():
144
ap = argparse.ArgumentParser()
145
ap.add_argument(
"--dir"
, dest=
"yamldir"
, required=
True
,
146
help=
"Directory where the test_merge_*.yaml/json files live"
)
147
ap.add_argument(
"--case"
, default=
"all"
,
148
choices=[
149
"all"
,
"earlier_wins"
,
"earlier_wins_between_fragments"
,
150
"list_concat"
,
"single_string_include"
],
151
help=
"Which case to run (default: all)"
)
152
args = ap.parse_args()
153
154
# Avoid DATAPATH interference (your _find_fragment should handle empty)
155
os.environ[
"DATAPATH"
] =
""
156
from
PathResolver
import
PathResolver
157
base_dir =
PathResolver.FindCalibDirectory
(args.yamldir)
158
base_dir = pathlib.Path(base_dir)
159
if
not
base_dir.is_dir():
160
sys.exit(f
"--dir {base_dir} is not a directory"
)
161
162
run_case
(args.case, base_dir)
163
164
if
__name__ ==
"__main__"
:
165
main
()
print
void print(char *figname, TCanvas *c1)
Definition
TRTCalib_StrawStatusPlots.cxx:26
PathResolver::FindCalibDirectory
static std::string FindCalibDirectory(const std::string &logical_file_name)
Definition
PathResolver.h:64
python.ConfigText_merge_unitTest._assert
_assert(cond, msg)
Definition
ConfigText_merge_unitTest.py:26
python.ConfigText_merge_unitTest.case_list_concat
case_list_concat(pathlib.Path base_dir)
Definition
ConfigText_merge_unitTest.py:86
python.ConfigText_merge_unitTest.case_earlier_wins
case_earlier_wins(pathlib.Path base_dir)
Definition
ConfigText_merge_unitTest.py:38
python.ConfigText_merge_unitTest.case_single_string_include
case_single_string_include(pathlib.Path base_dir)
Definition
ConfigText_merge_unitTest.py:93
python.ConfigText_merge_unitTest.main
main()
Definition
ConfigText_merge_unitTest.py:143
python.ConfigText_merge_unitTest.case_earlier_wins_between_fragments
case_earlier_wins_between_fragments(pathlib.Path base_dir)
Definition
ConfigText_merge_unitTest.py:63
python.ConfigText_merge_unitTest._load_yaml
_load_yaml(pathlib.Path p)
Definition
ConfigText_merge_unitTest.py:22
python.ConfigText_merge_unitTest._run_merge
_run_merge(dict top_cfg, pathlib.Path base_dir)
Definition
ConfigText_merge_unitTest.py:30
python.ConfigText_merge_unitTest.run_case
run_case(str name, pathlib.Path base_dir)
Definition
ConfigText_merge_unitTest.py:119
Generated on
for ATLAS Offline Software by
1.17.0