Repository: ioos/compliance-checker
Pull request #523 (2017-10-06 21:19:37)

Change error message, refactor logic for long/standard name detection
Puts an error message where neither standard_name nor long_name in
section 3.2 instead of section 3.3.  Refactors checker logic and
rewrites unit tests.  Addresses #506.


Patch:
diff --git a/compliance_checker/acdd.py b/compliance_checker/acdd.py
index 45d9208..e14e920 100644
--- a/compliance_checker/acdd.py
+++ b/compliance_checker/acdd.py
@@ -16,7 +16,6 @@ from compliance_checker.base import (BaseCheck, BaseNCCheck, check_has,
 from compliance_checker.cf.util import _possiblexunits, _possibleyunits
 from compliance_checker.util import datetime_is_iso, dateparse
 from compliance_checker import cfutil
-import pendulum
 from pygeoif import from_wkt
 
 
@@ -473,15 +472,8 @@ class ACDDBaseCheck(BaseCheck):
 
         # Time should be monotonically increasing, so we make that assumption here so we don't have to download THE ENTIRE ARRAY
         try:
-            # num2date returns as naive date, but with time adjusted to UTC
-            # we need to attach timezone information here, or the date
-            # subtraction from t_min/t_max will assume that a naive timestamp is
-            # in the same time zone and cause erroneous results.
-            # Pendulum uses UTC by default, but we are being explicit here
-            time0 = pendulum.instance(num2date(ds.variables[timevar][0],
-                                      ds.variables[timevar].units), 'UTC')
-            time1 = pendulum.instance(num2date(ds.variables[timevar][-1],
-                                      ds.variables[timevar].units), 'UTC')
+            time0 = num2date(ds.variables[timevar][0], ds.variables[timevar].units)
+            time1 = num2date(ds.variables[timevar][-1], ds.variables[timevar].units)
         except:
             return Result(BaseCheck.MEDIUM,
                           False,
diff --git a/compliance_checker/cf/cf.py b/compliance_checker/cf/cf.py
index 99d55ca..1ce8e23 100644
--- a/compliance_checker/cf/cf.py
+++ b/compliance_checker/cf/cf.py
@@ -1106,6 +1106,14 @@ class CFBaseCheck(BaseCheck):
 
             standard_name = getattr(ncvar, 'standard_name', None)
             standard_name, standard_name_modifier = self._split_standard_name(standard_name)
+            long_name = getattr(ncvar, 'long_name', None)
+            long_or_std_name = TestCtx(BaseCheck.HIGH, '§3.2 Either long_name or standard_name is highly recommended for variable {}'.format(name))
+            if long_name is not None:
+                long_name_present = True
+                long_or_std_name.assert_true(isinstance(long_name, basestring),
+                                             "Attribute long_name for variable {} must be a string".format(name))
+            else:
+                long_name_present = False
             # §1.3 The long_name and standard_name attributes are used to
             # describe the content of each variable. For backwards
             # compatibility with COARDS neither is required, but use of at
@@ -1113,38 +1121,39 @@ class CFBaseCheck(BaseCheck):
 
             # If standard_name is not defined but long_name is, don't continue
             # the check for this variable
-            if standard_name is None:
-                long_name = getattr(ncvar, 'long_name', None)
-                if long_name is not None:
-                    continue
-
-            valid_std_name = TestCtx(BaseCheck.HIGH, '§3.3 Variable {} has valid standard_name attribute'.format(name))
-
-            valid_std_name.assert_true(isinstance(standard_name, basestring),
-                                       "variable {}'s attribute standard_name must be a non-empty string "
-                                       "or it should define a long_name attribute.".format(name))
-
-            if isinstance(standard_name, basestring):
-                valid_std_name.assert_true(standard_name in self._std_names,
-                                           "standard_name {} is not defined in Standard Name Table v{}".format(
-                                               standard_name or 'undefined',
-                                               self._std_names._version))
-
-            ret_val.append(valid_std_name.to_result())
-
-            # 2) optional - if modifiers, should be in table
-            if standard_name_modifier is not None:
-                valid_modifier = TestCtx(BaseCheck.HIGH, "§3.3 standard_name modifier for {} is valid".format(name))
-                allowed = ['detection_minimum',
-                           'number_of_observations',
-                           'standard_error',
-                           'status_flag']
-                valid_modifier.assert_true(standard_name_modifier in allowed,
-                                           "standard_name modifier {} is not a valid modifier "
-                                           "according to appendix C".format(standard_name_modifier))
-
-                ret_val.append(valid_modifier.to_result())
+            if standard_name is not None:
+                standard_name_present = True
+                valid_std_name = TestCtx(BaseCheck.HIGH, '§3.3 Variable {} has valid standard_name attribute'.format(name))
+                valid_std_name.assert_true(isinstance(standard_name, basestring),
+                                        "Attribute standard_name for variable {} must be a string".format(name))
+
+                if isinstance(standard_name, basestring):
+                    valid_std_name.assert_true(standard_name in self._std_names,
+                                            "standard_name {} is not defined in Standard Name Table v{}".format(
+                                                standard_name or 'undefined',
+                                                self._std_names._version))
+
+                ret_val.append(valid_std_name.to_result())
+
+                # 2) optional - if modifiers, should be in table
+                if standard_name_modifier is not None:
+                    valid_modifier = TestCtx(BaseCheck.HIGH, "§3.3 standard_name modifier for {} is valid".format(name))
+                    allowed = ['detection_minimum',
+                            'number_of_observations',
+                            'standard_error',
+                            'status_flag']
+                    valid_modifier.assert_true(standard_name_modifier in allowed,
+                                            "standard_name modifier {} is not a valid modifier "
+                                            "according to appendix C".format(standard_name_modifier))
+
+                    ret_val.append(valid_modifier.to_result())
+            else:
+                standard_name_present = False
 
+            long_or_std_name.assert_true(long_name_present or
+                                            standard_name_present,
+                                            "Attribute long_name or/and standard_name is highly recommended for variable {}".format(name))
+            ret_val.append(long_or_std_name.to_result())
         return ret_val
 
     def check_ancillary_variables(self, ds):
@@ -1521,14 +1530,10 @@ class CFBaseCheck(BaseCheck):
 
             # Check that latitude uses allowed units
             allowed_units = TestCtx(BaseCheck.MEDIUM, '§4.1 Latitude variable {} uses recommended units'.format(latitude))
-            if standard_name == 'grid_latitude':
-                e_n_units = cfutil.VALID_LAT_UNITS + cfutil.VALID_LON_UNITS
-                # check that the units aren't in east and north degrees units,
-                # but are convertible to angular units
-                allowed_units.assert_true(units not in e_n_units and
-                                          Unit(units) == Unit('degree'),
-                                          "Grid latitude variable '{}' should use degree equivalent units without east or north components. "
-                                          "Current units are {}".format(latitude, units))
+            if 'rotated_latitude_longitude' in grid_mapping and standard_name == 'grid_latitude':
+                allowed_units.assert_true(units == 'degrees',
+                                          "latitude variable '{}' should use degrees for units in rotated pole grid"
+                                          "".format(latitude))
             else:
                 allowed_units.assert_true(units_is_string and units.lower() in allowed_lat_units,
                                           "latitude variable '{}' should define valid units for latitude"
@@ -1615,14 +1620,10 @@ class CFBaseCheck(BaseCheck):
 
             # Check that longitude uses allowed units
             allowed_units = TestCtx(BaseCheck.MEDIUM, '§4.1 Longitude variable {} uses recommended units'.format(longitude))
-            if standard_name == 'grid_longitude':
-                e_n_units = cfutil.VALID_LAT_UNITS + cfutil.VALID_LON_UNITS
-                # check that the units aren't in east and north degrees units,
-                # but are convertible to angular units
-                allowed_units.assert_true(units not in e_n_units and
-                                          Unit(units) == Unit('degree'),
-                                          "Grid longitude variable '{}' should use degree equivalent units without east or north components. "
-                                          "Current units are {}".format(longitude, units))
+            if 'rotated_latitude_longitude' in grid_mapping and standard_name == 'grid_longitude':
+                allowed_units.assert_true(units == 'degrees',
+                                          "longitude variable '{}' should use degrees for units in rotated pole grid"
+                                          "".format(longitude))
             else:
                 allowed_units.assert_true(units_is_string and units.lower() in allowed_lon_units,
                                           "longitude variable '{}' should define valid units for longitude"
@@ -2159,7 +2160,7 @@ class CFBaseCheck(BaseCheck):
             # contents of the `coordinates` attribute only.
             axis_map = cfutil.get_axis_map(ds, variable)
 
-            # Make sure we can find latitude and its dimensions are a subset
+            # Make sure we can find latitude and it's dimensions are a subset
             found_lat = False
             for lat in axis_map['Y']:
                 is_subset_dims = set(ds.variables[lat].dimensions).issubset(dimensions)
@@ -2171,7 +2172,7 @@ class CFBaseCheck(BaseCheck):
                                    '{} is not associated with a coordinate defining true latitude '
                                    'and sharing a subset of dimensions'.format(variable))
 
-            # Make sure we can find longitude and its dimensions are a subset
+            # Make sure we can find longitude and it's dimensions are a subset
             found_lon = False
             for lon in axis_map['X']:
                 is_subset_dims = set(ds.variables[lon].dimensions).issubset(dimensions)
@@ -2296,7 +2297,7 @@ class CFBaseCheck(BaseCheck):
         ret_val = []
         grid_mapping_variables = cfutil.get_grid_mapping_variables(ds)
 
-        # Check the grid_mapping attribute to be a non-empty string and that its reference exists
+        # Check the grid_mapping attribute to be a non-empty string and that it's reference exists
         for variable in ds.get_variables_by_attributes(grid_mapping=lambda x: x is not None):
             grid_mapping = getattr(variable, 'grid_mapping', None)
             defines_grid_mapping = TestCtx(BaseCheck.HIGH,
@@ -2643,7 +2644,6 @@ class CFBaseCheck(BaseCheck):
 
         return ret_val
 
-
     def check_cell_methods(self, ds):
         """
         7.3 To describe the characteristic of a field that is represented by cell values, we define the cell_methods attribute
@@ -2682,8 +2682,9 @@ class CFBaseCheck(BaseCheck):
         }
 
         ret_val = []
-        psep = regex.compile(r'(?P<vars>\w+: )+(?P<method>\w+) ?(?P<where>where (?P<wtypevar>\w+) '
-                             '?(?P<over>over (?P<otypevar>\w+))?| ?)(?:\((?P<paren_contents>[^)]*)\))?')
+        psep = regex.compile(r'((?P<vars>\w+: )+(?P<method>\w+) ?(?P<where>where (?P<wtypevar>\w+) '
+                              '?(?P<over>over (?P<otypevar>\w+))?| ?)(?P<brace>\(((?P<brace_wunit>\w+): '
+                              '(?P<interval_val>\w+) (?P<interval_unit>\w+)|(?P<brace_opt>\w+): (\w+))\))?)*')
 
         for var in ds.get_variables_by_attributes(cell_methods=lambda x: x is not None):
             if not getattr(var, 'cell_methods', ''):
@@ -2727,95 +2728,44 @@ class CFBaseCheck(BaseCheck):
                                          '§7.3 {} has valid methods in cell_methods attribute'.format(var.name))
 
             for match in regex.finditer(psep, method):
-                # CF section 7.3 - "Case is not significant in the method name."
-                valid_cell_methods.assert_true(match.group('method').lower() in methods,
+                valid_cell_methods.assert_true(match.group('method') in methods,
                                                '{}:cell_methods contains an invalid method: {}'
                                                ''.format(var.name, match.group('method')))
 
             ret_val.append(valid_cell_methods.to_result())
 
+            valid_modifier = TestCtx(BaseCheck.MEDIUM,
+                                     '§7.3.3 {} has valid cell_methods modifiers'.format(var.name))
+
             for match in regex.finditer(psep, method):
-                if match.group('paren_contents') is not None:
-                    # split along spaces followed by words with a colon
-                    # not sure what to do if a comment contains a colon!
-                    ret_val.append(self._check_cell_methods_paren_info(match.group('paren_contents'), var).to_result())
+                if match.group('brace') is not None:
+                    valid_modifier.assert_true(match.group('brace_wunit') in {'interval', 'comment', 'area'},
+                                               '{}:cell_methods contains an invalid modifier: {}. It should be one '
+                                               'of interval, comment or area.'
+                                               ''.format(var.name, match.group('brace_wunit')))
+                    if match.group('brace_wunit') == 'interval':
+                        # attempt to get the number for the interval
+                        valid_modifier.out_of += 2
+                        try:
+                            float(match.group('interval_val'))
+                        except ValueError:
+                            valid_modifier.messages.append('{}:cell_methods contains an interval value that does not parse as a numeric value: "{}".'.format(var.name, match.group('interval_val')))
+                        else:
+                            valid_modifier.score += 1
+
+                        # then the units
+                        try:
+                            Unit(match.group('interval_unit'))
+                        except ValueError:
+                            valid_modifier.messages.append('{}:cell_methods interval units "{}" is not parsable by UDUNITS.'.format(var.name, match.group('interval_unit')))
+                        else:
+                            valid_modifier.score += 1
+
+            if valid_modifier.out_of > 0:
+                ret_val.append(valid_modifier.to_result())
 
         return ret_val
 
-    def _check_cell_methods_paren_info(self, paren_contents, var):
-        """
-        Checks that the spacing and/or comment info contained inside the
-        parentheses in cell_methods is well-formed
-        """
-        #valid_info = TestCtx(BaseCheck.MEDIUM,
-        #                            '§7.3.2 {} has valid cell_methods spacing/other info'.format(var.name))
-        valid_info = TestCtx(BaseCheck.MEDIUM,
-                             '§7.3.3 {} has valid cell_methods modifiers'.format(var.name))
-        # if there are no colons, this is a simple comment
-        # TODO: are empty comments considered valid?
-        if ':' not in paren_contents:
-            valid_info.out_of += 1
-            valid_info.score += 1
-            return valid_info
-        # otherwise, split into k/v pairs
-        kv_pair_pat = r'(\S+:)\s+(.*(?=\s+\w+:)|[^:]+$)\s*'
-        # otherwise, we must split further with intervals coming
-        # first, followed by non-standard comments
-        # we need the count of the matches, and re.findall() only returns
-        # groups if they are present and we wish to see if the entire match
-        # object concatenated together is the same as the original string
-        pmatches = [m for m in regex.finditer(kv_pair_pat, paren_contents)]
-        for i, pmatch in enumerate(pmatches):
-            keyword, val = pmatch.groups()
-            if keyword == 'interval:':
-                valid_info.out_of += 2
-                interval_matches = regex.match(r'^\s*(?P<interval_number>\S+)\s+(?P<interval_units>\S+)\s*$', val)
-                # attempt to get the number for the interval
-                if not interval_matches:
-                    valid_info.messages.append('{}:cell_methods contains an interval specification that does not parse: "{}". Should be in format "interval: <number> <units>"'.format(var.name, val))
-                else:
-                    try:
-                        float(interval_matches.group('interval_number'))
-                    except ValueError:
-                        valid_info.messages.append('{}:cell_methods contains an interval value that does not parse as a numeric value: "{}".'.format(var.name, interval_matches.group('interval_number')))
-                    else:
-                        valid_info.score += 1
-
-                    # then the units
-                    try:
-                        Unit(interval_matches.group('interval_units'))
-                    except ValueError:
-                        valid_info.messages.append('{}:cell_methods interval units "{}" is not parsable by UDUNITS.'.format(var.name, interval_matches.group('interval_units')))
-                    else:
-                        valid_info.score += 1
-            elif keyword == 'comment:':
-                # comments can't really be invalid, except
-                # if they come first or aren't last, and
-                # maybe if they contain colons embedded in the
-                # comment string
-                valid_info.out_of += 1
-                if len(pmatches) == 1:
-                    valid_info.messages.append('If there is no standardized information, the keyword comment: should be omitted for variable {}'.format(var.name))
-                # otherwise check that the comment is the last
-                # item in the parentheses
-                elif i != len(pmatches) - 1:
-                    valid_info.messages.append('The non-standard "comment:" element must come after any standard elements in cell_methods for variable {}'.format(var.name))
-                #
-                else:
-                    valid_info.score += 1
-            else:
-                valid_info.out_of += 1
-                valid_info.messages.append('Invalid cell_methods keyword "{}" for variable {}. Must be one of [interval, comment]'.format(keyword, var.name))
-
-
-        # Ensure concatenated reconstructed matches are the same as the
-        # original string.  If they're not, there's likely a formatting error
-        valid_info.assert_true(''.join(m.group(0)
-                                       for m in pmatches) == paren_contents,
-                   "Parenthetical content inside cell_methods is not well formed: {}".format(paren_contents))
-
-        return valid_info
-
     def check_climatological_statistics(self, ds):
         """
         7.4 A climatological time coordinate variable does not have a bounds attribute. Instead, it has a climatology
diff --git a/requirements.txt b/requirements.txt
index 43456ce..cd6b5da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,6 +6,6 @@ isodate>=0.5.4
 Jinja2>=2.7.3
 setuptools>=15.0
 pygeoif>=0.6
-netCDF4>=1.3.0
+netCDF4>=1.2.2
 regex==2017.07.28
 pendulum>=1.2.4


Test patch:
diff --git a/compliance_checker/tests/helpers.py b/compliance_checker/tests/helpers.py
index e369b65..421bc34 100644
--- a/compliance_checker/tests/helpers.py
+++ b/compliance_checker/tests/helpers.py
@@ -30,12 +30,7 @@ class MockTimeSeries(MockNetCDF):
 
 class MockVariable(object):
     '''
-    For mocking a dataset variable. Constructor optionally takes a NetCDF
-    variable, the NetCDF attributes of which will be copied over to this
-    object.
+    For mocking a dataset variable
     '''
+    pass
 
-    def __init__(self, copy_var=None):
-        if copy_var is not None:
-            for att in copy_var.ncattrs():
-                setattr(self, att, getattr(copy_var, att))
diff --git a/compliance_checker/tests/test_acdd.py b/compliance_checker/tests/test_acdd.py
index 0c37ca2..65b2416 100644
--- a/compliance_checker/tests/test_acdd.py
+++ b/compliance_checker/tests/test_acdd.py
@@ -435,11 +435,7 @@ class TestACDD1_3(BaseTestCase):
         time_var[:] = [1451692800]  # 20160102T000000Z in seconds since epoch
         empty_ds.time_coverage_start = '20160102T000000Z'
         empty_ds.time_coverage_end = '20160102T000000Z'
-        result = self.acdd.check_time_extents(empty_ds)
-        self.assert_result_is_good(result)
-        # try the same thing with time offsets
-        time_var.units = 'seconds since 1970-01-01 00:00:00-10:00'
-        empty_ds.time_coverage_start = '20160102T000000-1000'
-        empty_ds.time_coverage_end = '20160102T000000-1000'
+
+
         result = self.acdd.check_time_extents(empty_ds)
         self.assert_result_is_good(result)
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index 89d54f9..5a435e0 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -279,13 +279,14 @@ class TestCF(BaseTestCase):
         dataset = self.load_dataset(STATIC_FILES['bad_data_type'])
         results = self.cf.check_standard_name(dataset)
         result_dict = {result.name: result for result in results}
-        result = result_dict[u'§3.3 Variable time has valid standard_name attribute']
+        result = result_dict[u'§3.2 Either long_name or standard_name is highly recommended for variable time']
         assert result.value == (0, 1)
-        assert 'variable time\'s attribute standard_name must be a non-empty string or it should define a long_name attribute.' == result.msgs[0]
+        assert "Attribute long_name or/and standard_name is highly recommended for variable time" in result.msgs
 
-        result = result_dict[u'§3.3 Variable latitude has valid standard_name attribute']
+        result = result_dict[u'§3.2 Either long_name or standard_name is highly recommended for variable latitude']
         assert result.value == (0, 1)
-        assert 'variable latitude\'s attribute standard_name must be a non-empty string or it should define a long_name attribute.' == result.msgs[0]
+        assert "Attribute long_name or/and standard_name is highly recommended for variable latitude" in result.msgs
+        #assert 'variable latitude\'s attribute standard_name must be a non-empty string or it should define a long_name attribute.' == result.msgs[0]
 
         result = result_dict[u'§3.3 Variable salinity has valid standard_name attribute']
         assert result.value == (1, 2)
@@ -294,14 +295,14 @@ class TestCF(BaseTestCase):
         result = result_dict[u'§3.3 standard_name modifier for salinity is valid']
         assert result.value == (0, 1)
 
-        assert len(result_dict) == 8
+        assert len(result_dict) == 9
 
         dataset = self.load_dataset(STATIC_FILES['reduced_horizontal_grid'])
         results = self.cf.check_standard_name(dataset)
         score, out_of, messages = self.get_results(results)
         # Make sure that the rgrid coordinate variable isn't checked for standard_name
         # time, lat, lon exist with three checks each
-        assert (score, out_of) == (6, 6)
+        assert (score, out_of) == (11, 11)
 
     def test_cell_bounds(self):
         dataset = self.load_dataset(STATIC_FILES['grid-boundaries'])
@@ -546,20 +547,6 @@ class TestCF(BaseTestCase):
         results = self.cf.check_latitude(dataset)
         scored, out_of, messages = self.get_results(results)
         assert (scored, out_of) == (6, 6)
-        # hack to avoid writing to read-only file
-        dataset.variables['rlat'] = MockVariable(dataset.variables['rlat'])
-        rlat = dataset.variables['rlat']
-        rlat.name = 'rlat'
-        # test with a bad value
-        rlat.units = 'degrees_north'
-        results = self.cf.check_latitude(dataset)
-        scored, out_of, messages = self.get_results(results)
-        wrong_format = "Grid latitude variable '{}' should use degree equivalent units without east or north components. Current units are {}"
-        self.assertTrue(wrong_format.format(rlat.name, rlat.units) in messages)
-        rlat.units = 'radians'
-        results = self.cf.check_latitude(dataset)
-        scored, out_of, messages = self.get_results(results)
-        self.assertTrue(wrong_format.format(rlat.name, rlat.units) in messages)
 
     def test_longitude(self):
         '''
@@ -611,20 +598,6 @@ class TestCF(BaseTestCase):
         results = self.cf.check_latitude(dataset)
         scored, out_of, messages = self.get_results(results)
         assert (scored, out_of) == (6, 6)
-        # hack to avoid writing to read-only file
-        dataset.variables['rlon'] = MockVariable(dataset.variables['rlon'])
-        rlon = dataset.variables['rlon']
-        rlon.name = 'rlon'
-        # test with a bad value
-        rlon.units = 'degrees_east'
-        results = self.cf.check_longitude(dataset)
-        scored, out_of, messages = self.get_results(results)
-        wrong_format = "Grid longitude variable '{}' should use degree equivalent units without east or north components. Current units are {}"
-        self.assertTrue(wrong_format.format(rlon.name, rlon.units) in messages)
-        rlon.units = 'radians'
-        results = self.cf.check_longitude(dataset)
-        scored, out_of, messages = self.get_results(results)
-        self.assertTrue(wrong_format.format(rlon.name, rlon.units) in messages)
 
     def test_is_vertical_coordinate(self):
         '''
@@ -1011,25 +984,6 @@ class TestCF(BaseTestCase):
                         in messages)
         self.assertTrue('temperature:cell_methods interval units "whizbangs" is not parsable by UDUNITS.'
                         in messages)
-        temp.cell_methods = 'lat: lon: mean depth: mean (comment: should not go here interval: 2.5 m)'
-        results = self.cf.check_cell_methods(nc_obj)
-        self.assertTrue('The non-standard "comment:" element must come after any standard elements in cell_methods for variable temperature')
-        # standalone comments require no keyword
-        temp.cell_methods = 'lon: mean (This is a standalone comment)'
-        results = self.cf.check_cell_methods(nc_obj)
-        result_dict = {result.name: result for result in results}
-        modifier_results = result_dict[u'§7.3.3 temperature has valid cell_methods modifiers']
-        self.assertTrue(modifier_results.value == (1, 1))
-        temp.cell_methods = 'lat: lon: mean depth: mean (invalid_keyword: this is invalid)'
-        results = self.cf.check_cell_methods(nc_obj)
-        self.assertTrue('Invalid cell_methods keyword "invalid_keyword" for variable temperature. Must be one of [interval, comment]')
-        temp.cell_methods = 'lat: lon: mean depth: mean (interval: 0.2 m comment: This should come last interval: 0.01 degrees)'
-        results = self.cf.check_cell_methods(nc_obj)
-        self.assertTrue('The non-standard "comment:" element must come after any standard elements in cell_methods for variable temperature')
-        temp.cell_methods = 'lat: lon: mean depth: mean (interval 0.2 m interval: 0.01 degrees)'
-        results = self.cf.check_cell_methods(nc_obj)
-        self.assertTrue('Parenthetical content inside cell_methods is not well formed: interval 0.2 m interval: 0.01 degrees')
-
 
 
     # --------------------------------------------------------------------------------