2
2
# SPDX-License-Identifier: Apache-2.0
3
3
4
4
import pytest
5
- from unittest . mock import mock_open , patch
5
+ from lark . exceptions import UnexpectedCharacters , UnexpectedEOF
6
6
from pathlib import Path
7
7
8
- from access .parsers .nuopc_config import read_nuopc_config , write_nuopc_config
8
+ from access .parsers .nuopc_config import NUOPCParser
9
9
10
10
11
- @pytest .fixture ()
12
- def simple_nuopc_config ():
11
+ @pytest .fixture (scope = "module" )
12
+ def parser ():
13
+ """Fixture instantiating the parser."""
14
+ return NUOPCParser ()
15
+
16
+
17
+ @pytest .fixture (scope = "module" )
18
+ def nuopc_config ():
19
+ """Fixture returning a dict holding the parsed content of a NUOPC config file."""
13
20
return dict (
14
21
DRIVER_attributes = {
15
22
"Verbosity" : "off" ,
16
23
"cime_model" : "cesm" ,
17
- "logFilePostFix" : ".log" ,
24
+ "logFilePostFix" : Path ( ".log" ) ,
18
25
"pio_blocksize" : - 1 ,
19
26
"pio_rearr_comm_enable_hs_comp2io" : True ,
20
27
"pio_rearr_comm_enable_hs_io2comp" : False ,
@@ -23,73 +30,145 @@ def simple_nuopc_config():
23
30
"wv_sat_transition_start" : 20.0 ,
24
31
},
25
32
COMPONENTS = ["atm" , "ocn" ],
33
+ TEST = "On" ,
26
34
ALLCOMP_attributes = {
27
35
"ATM_model" : "datm" ,
28
36
"GLC_model" : "sglc" ,
29
37
"OCN_model" : "mom" ,
30
- "ocn2glc_levels" : "1:10:19:26:30:33:35" ,
38
+ "ocn2glc_levels" : [ 1 , 10 , 19 , 26 , 30 , 33 , 35 ] ,
31
39
},
32
40
)
33
41
34
42
35
- @pytest .fixture ()
36
- def simple_nuopc_config_file ():
37
- return """DRIVER_attributes::
43
+ @pytest .fixture (scope = "module" )
44
+ def nuopc_config_file ():
45
+ """Fixture returning the content of a NUOPC config file."""
46
+ return """DRIVER_attributes:: # Comment 1
47
+
38
48
Verbosity = off
39
- cime_model = cesm
49
+ cime_model = cesm # Comment 2
50
+
40
51
logFilePostFix = .log
41
52
pio_blocksize = -1
53
+
54
+ # Comment 3
55
+
42
56
pio_rearr_comm_enable_hs_comp2io = .true.
43
57
pio_rearr_comm_enable_hs_io2comp = .false.
44
58
reprosum_diffmax = -1.000000D-08
45
59
wv_sat_table_spacing = 1.000000D+00
46
60
wv_sat_transition_start = 2.000000D+01
47
61
::
48
62
49
- COMPONENTS: atm ocn
63
+ TEST: On
64
+
65
+ # Comment 4
66
+ # Comment 5
67
+
68
+ COMPONENTS: atm ocn # Comment 6
69
+
50
70
ALLCOMP_attributes::
71
+
51
72
ATM_model = datm
52
73
GLC_model = sglc
53
74
OCN_model = mom
54
75
ocn2glc_levels = 1:10:19:26:30:33:35
76
+
55
77
::
56
78
57
79
"""
58
80
59
81
60
- @pytest .fixture ()
61
- def invalid_nuopc_config_file ():
62
- return """DRIVER_attributes::
63
- Verbosity: off
64
- cime_model - cesm
82
+ @pytest .fixture (scope = "module" )
83
+ def modified_nuopc_config_file ():
84
+ """Fixture returning the content of the previous NUOPC config file, but with some modifications."""
85
+ return """DRIVER_attributes:: # Comment 1
86
+
87
+ Verbosity = off
88
+ cime_model = cesm # Comment 2
89
+
90
+ logFilePostFix = .log
91
+ pio_blocksize = -1
92
+
93
+ # Comment 3
94
+
95
+ pio_rearr_comm_enable_hs_comp2io = .true.
96
+ pio_rearr_comm_enable_hs_io2comp = .false.
97
+ reprosum_diffmax = -1.000000D-08
98
+ wv_sat_table_spacing = 1.000000D+00
99
+ wv_sat_transition_start = 2.000000D+01
100
+ ::
101
+
102
+ TEST: Off
103
+
104
+ # Comment 4
105
+ # Comment 5
106
+
107
+ COMPONENTS: atm um # Comment 6
108
+
109
+ ALLCOMP_attributes::
110
+
111
+ ATM_model = um
112
+ GLC_model = sglc
113
+ OCN_model = mom
114
+ ocn2glc_levels = 1:10:19:26:30:33:36
115
+
65
116
::
66
117
67
- COMPONENTS::: atm ocn
68
118
"""
69
119
70
120
71
- @patch ("pathlib.Path.is_file" , new = lambda file : True )
72
- def test_read_nuopc_config (simple_nuopc_config , simple_nuopc_config_file ):
73
- with patch ("builtins.open" , mock_open (read_data = simple_nuopc_config_file )) as m :
74
- config = read_nuopc_config (file_name = "simple_nuopc_config_file" )
121
+ def test_valid_nuopc_config (parser ):
122
+ """Test the basic grammar constructs"""
123
+ assert dict (parser .parse ("TEST: a" )) == {"TEST" : "a" }
124
+ assert dict (parser .parse (" TEST:a" )) == {"TEST" : "a" }
125
+ assert dict (parser .parse ("TEST: a b" )) == {"TEST" : ["a" , "b" ]}
126
+ assert dict (parser .parse ("TEST1: a\n TEST2: b" )) == {"TEST1" : "a" , "TEST2" : "b" }
127
+ assert dict (parser .parse ("TEST1: a \n TEST2: b" )) == {"TEST1" : "a" , "TEST2" : "b" }
128
+ assert dict (parser .parse ("TEST1: a b \n TEST2: c" )) == {"TEST1" : ["a" , "b" ], "TEST2" : "c" }
129
+ assert dict (parser .parse ("TEST::\n a=1\n ::" )) == {"TEST" : {"a" : 1 }}
130
+ assert dict (parser .parse ("TEST::\n a=1:2:3\n ::" )) == {"TEST" : {"a" : [1 , 2 , 3 ]}}
131
+
132
+
133
+ def test_invalid_nuopc_config (parser ):
134
+ """Test checking that the parser catches malformed expressions"""
135
+ with pytest .raises (UnexpectedCharacters ):
136
+ parser .parse ("TEST::\n cime_model - cesm" )
137
+
138
+ with pytest .raises (UnexpectedCharacters ):
139
+ parser .parse ("TEST:\n cime_model = cesm\n ::" )
140
+
141
+ with pytest .raises (UnexpectedEOF ):
142
+ parser .parse ("TEST::\n cime_model = cesm" )
143
+
144
+ with pytest .raises (UnexpectedCharacters ):
145
+ parser .parse ("TEST::\n cime_model = cesm ATM_model = datm" )
146
+
75
147
76
- assert config == simple_nuopc_config
148
+ def test_nuopc_config_parse (parser , nuopc_config , nuopc_config_file ):
149
+ """Test parsing of a file."""
150
+ config = parser .parse (nuopc_config_file )
151
+ assert dict (config ) == nuopc_config
77
152
78
153
79
- def test_write_nuopc_config ( simple_nuopc_config , simple_nuopc_config_file ):
80
- with patch ( "builtins.open" , mock_open ()) as m :
81
- write_nuopc_config ( simple_nuopc_config , Path ( "config_file" ) )
154
+ def test_nuopc_config_roundtrip ( parser , nuopc_config_file ):
155
+ """Test round-trip parsing."""
156
+ config = parser . parse ( nuopc_config_file )
82
157
83
- assert simple_nuopc_config_file == "" . join ( call . args [ 0 ] for call in m (). write . mock_calls )
158
+ assert str ( config ) == nuopc_config_file
84
159
85
160
86
- @patch ("pathlib.Path.is_file" , new = lambda file : True )
87
- def test_read_invalid_nuopc_config_file (invalid_nuopc_config_file ):
88
- with patch ("builtins.open" , mock_open (read_data = invalid_nuopc_config_file )) as m :
89
- with pytest .raises (ValueError ):
90
- read_nuopc_config (file_name = "invalid_nuopc_config_file" )
161
+ def test_nuopc_config_roundtrip_with_mutation (parser , nuopc_config_file , modified_nuopc_config_file ):
162
+ """Test round-trip parsing with mutation of the config."""
163
+ config = parser .parse (nuopc_config_file )
91
164
165
+ # Scalar
166
+ config ["TEST" ] = "Off"
167
+ # List
168
+ config ["COMPONENTS" ] = ["atm" , "um" ]
169
+ # Scalar in table
170
+ config ["ALLCOMP_attributes" ]["ATM_model" ] = "um"
171
+ # List in table
172
+ config ["ALLCOMP_attributes" ]["ocn2glc_levels" ] = [1 , 10 , 19 , 26 , 30 , 33 , 36 ]
92
173
93
- def test_read_missing_nuopc_config_file ():
94
- with pytest .raises (FileNotFoundError ):
95
- read_nuopc_config (file_name = "garbage" )
174
+ assert str (config ) == modified_nuopc_config_file
0 commit comments