11"""Unit tests for :mod:`esmvalcore.cmor.fix`."""
22
3+ from __future__ import annotations
4+
5+ from collections .abc import Sequence
36from pathlib import Path
7+ from typing import TYPE_CHECKING
48from unittest .mock import Mock , patch , sentinel
59
10+ import iris
11+ import iris .cube
612import pytest
713
814from esmvalcore .cmor .fix import Fix , fix_data , fix_file , fix_metadata
15+ from esmvalcore .io .local import LocalFile
16+ from esmvalcore .io .protocol import DataElement
17+
18+ if TYPE_CHECKING :
19+ from pytest_mock import MockerFixture
920
1021
1122class TestFixFile :
@@ -14,9 +25,9 @@ class TestFixFile:
1425 @pytest .fixture (autouse = True )
1526 def setUp (self ):
1627 """Prepare for testing."""
17- self .filename = "filename"
28+ self .filename = Path ( "filename" )
1829 self .mock_fix = Mock ()
19- self .mock_fix .fix_file .return_value = "new_filename"
30+ self .mock_fix .fix_file .return_value = Path ( "new_filename" )
2031 self .expected_get_fixes_call = {
2132 "project" : "project" ,
2233 "dataset" : "model" ,
@@ -33,48 +44,106 @@ def setUp(self):
3344 "frequency" : "frequency" ,
3445 }
3546
36- def test_fix (self ) :
47+ def test_fix (self , mocker : MockerFixture ) -> None :
3748 """Check that the returned fix is applied."""
38- with patch (
49+ mock_get_fixes = mocker . patch (
3950 "esmvalcore.cmor._fixes.fix.Fix.get_fixes" ,
4051 return_value = [self .mock_fix ],
41- ) as mock_get_fixes :
42- file_returned = fix_file (
43- file = "filename" ,
44- short_name = "short_name" ,
45- project = "project" ,
46- dataset = "model" ,
47- mip = "mip" ,
48- output_dir = Path ("output_dir" ),
49- session = sentinel .session ,
50- frequency = "frequency" ,
51- )
52- assert file_returned != self .filename
53- assert file_returned == "new_filename"
54- mock_get_fixes .assert_called_once_with (
55- ** self .expected_get_fixes_call ,
56- )
52+ )
53+ file_returned = fix_file (
54+ file = Path ( "filename" ) ,
55+ short_name = "short_name" ,
56+ project = "project" ,
57+ dataset = "model" ,
58+ mip = "mip" ,
59+ output_dir = Path ("output_dir" ),
60+ session = sentinel .session ,
61+ frequency = "frequency" ,
62+ )
63+ assert file_returned != self .filename
64+ assert file_returned == Path ( "new_filename" )
65+ mock_get_fixes .assert_called_once_with (
66+ ** self .expected_get_fixes_call ,
67+ )
5768
58- def test_nofix (self ):
69+ def test_fix_returns_cubes (
70+ self ,
71+ mocker : MockerFixture ,
72+ tmp_path : Path ,
73+ ) -> None :
74+ """Check that the returned fix is applied."""
75+ # Prepare some mock fixed data and save it to a file.
76+ fixed_file = LocalFile (tmp_path / "new_filename.nc" )
77+ fixed_cube = iris .cube .Cube ([0 ], var_name = "tas" )
78+ fixed_cube .attributes .globals = {"a" : "b" }
79+ iris .save (fixed_cube , fixed_file )
80+
81+ # Set up a mock fix to that returns this data.
82+ self .mock_fix .fix_file .return_value = fixed_file
83+ mock_get_fixes = mocker .patch (
84+ "esmvalcore.cmor._fixes.fix.Fix.get_fixes" ,
85+ return_value = [self .mock_fix ],
86+ )
87+
88+ mock_input_file = LocalFile (self .filename )
89+ result = fix_file (
90+ file = mock_input_file ,
91+ short_name = "short_name" ,
92+ project = "project" ,
93+ dataset = "model" ,
94+ mip = "mip" ,
95+ output_dir = Path ("output_dir" ),
96+ session = sentinel .session ,
97+ frequency = "frequency" ,
98+ )
99+ # Check that a sequence of cubes is returned and that the attributes
100+ # of the input file have been updated with the global attributes of the
101+ # fixed cube for recording provenance.
102+ assert isinstance (result , Sequence )
103+ assert len (result ) == 1
104+ assert isinstance (result [0 ], iris .cube .Cube )
105+ assert result [0 ].var_name == "tas"
106+ assert "a" in mock_input_file .attributes
107+ assert mock_input_file .attributes ["a" ] == "b"
108+ mock_get_fixes .assert_called_once_with (
109+ ** self .expected_get_fixes_call ,
110+ )
111+
112+ def test_nofix (self , mocker : MockerFixture ) -> None :
59113 """Check that the same file is returned if no fix is available."""
60- with patch (
114+ mock_get_fixes = mocker . patch (
61115 "esmvalcore.cmor._fixes.fix.Fix.get_fixes" ,
62116 return_value = [],
63- ) as mock_get_fixes :
64- file_returned = fix_file (
65- file = "filename" ,
66- short_name = "short_name" ,
67- project = "project" ,
68- dataset = "model" ,
69- mip = "mip" ,
70- output_dir = Path ("output_dir" ),
71- session = sentinel .session ,
72- frequency = "frequency" ,
73- )
74- assert file_returned == self .filename
75- mock_get_fixes .assert_called_once_with (
76- ** self .expected_get_fixes_call ,
77- )
117+ )
118+ file_returned = fix_file (
119+ file = Path ("filename" ),
120+ short_name = "short_name" ,
121+ project = "project" ,
122+ dataset = "model" ,
123+ mip = "mip" ,
124+ output_dir = Path ("output_dir" ),
125+ session = sentinel .session ,
126+ frequency = "frequency" ,
127+ )
128+ assert file_returned == self .filename
129+ mock_get_fixes .assert_called_once_with (
130+ ** self .expected_get_fixes_call ,
131+ )
132+
133+ def test_nofix_if_not_path (self , mocker : MockerFixture ) -> None :
134+ """Check that the same object is returned if the input is not a Path."""
135+ mock_data_element = mocker .create_autospec (DataElement , instance = True )
136+ file_returned = fix_file (
137+ file = mock_data_element ,
138+ short_name = "short_name" ,
139+ project = "project" ,
140+ dataset = "model" ,
141+ mip = "mip" ,
142+ output_dir = Path ("output_dir" ),
143+ session = sentinel .session ,
144+ frequency = "frequency" ,
145+ )
146+ assert file_returned is mock_data_element
78147
79148
80149class TestGetCube :
0 commit comments