nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: canonical/operator
Pull request #350 (2020-06-30 14:16:22)

Improve ops.lib line matching regexp
 *  correctly escaping hyphen
 *  optional whitespace before and after =
 *  let lines terminate with a comment

fixes: 341

jameinel: Note that I did add tests, and I think we still need to tweak the regex.
I don't think we want this:
https://github.com/canonical/operator/pull/350/files#diff-829bb8b4935480cde27493bcc2eca67eR221


Patch:
diff --git a/ops/lib/__init__.py b/ops/lib/__init__.py
index a4b8041..edb9fca 100644
--- a/ops/lib/__init__.py
+++ b/ops/lib/__init__.py
@@ -25,7 +25,7 @@ from types import ModuleType
 
 _libraries = None
 
-_libline_re = re.compile(r'''^LIB([A-Z]+)\s+=\s+([0-9]+|['"][a-zA-Z0-9_.-@]+['"])\s*$''')
+_libline_re = re.compile(r'''^LIB([A-Z]+)\s*=\s*([0-9]+|['"][a-zA-Z0-9_.\-@]+['"])''')
 _libname_re = re.compile(r'''^[a-z][a-z0-9]+$''')
 
 # Not perfect, but should do for now.


Test patch:
diff --git a/test/test_lib.py b/test/test_lib.py
index b44cfb6..7e9af85 100644
--- a/test/test_lib.py
+++ b/test/test_lib.py
@@ -187,6 +187,45 @@ class TestLibParser(TestCase):
         # also check the repr while we're at it
         self.assertEqual(repr(lib), '<_Lib foo by alice@example.com, API 2, patch 42>')
 
+    def test_libauthor_has_dashes(self):
+        m = self._mkmod('foo', '''
+        LIBNAME = "foo"
+        LIBAPI = 2
+        LIBPATCH = 42
+        LIBAUTHOR = "alice-someone@example.com"
+        LIBANANA = True
+        ''')
+        lib = ops.lib._parse_lib(m)
+        self.assertEqual(lib, ops.lib._Lib(None, "foo", "alice-someone@example.com", 2, 42))
+        # also check the repr while we're at it
+        self.assertEqual(repr(lib), '<_Lib foo by alice-someone@example.com, API 2, patch 42>')
+
+    def test_lib_definitions_without_spaces(self):
+        m = self._mkmod('foo', '''
+        LIBNAME="foo"
+        LIBAPI=2
+        LIBPATCH=42
+        LIBAUTHOR="alice@example.com"
+        LIBANANA=True
+        ''')
+        lib = ops.lib._parse_lib(m)
+        self.assertEqual(lib, ops.lib._Lib(None, "foo", "alice@example.com", 2, 42))
+        # also check the repr while we're at it
+        self.assertEqual(repr(lib), '<_Lib foo by alice@example.com, API 2, patch 42>')
+
+    def test_lib_definitions_trailing_comments(self):
+        m = self._mkmod('foo', '''
+        LIBNAME = "foo" # comment style 1
+        LIBAPI = 2 = comment style 2
+        LIBPATCH = 42
+        LIBAUTHOR = "alice@example.com"anything after the quote is a comment
+        LIBANANA = True
+        ''')
+        lib = ops.lib._parse_lib(m)
+        self.assertEqual(lib, ops.lib._Lib(None, "foo", "alice@example.com", 2, 42))
+        # also check the repr while we're at it
+        self.assertEqual(repr(lib), '<_Lib foo by alice@example.com, API 2, patch 42>')
+
     def test_incomplete(self):
         """Check that if anything is missing, nothing is returned"""
         m = self._mkmod('foo', '''
Repository: canonical/operator
Pull request #352 (2020-06-30 15:47:18)

make ops.lib.use support relative submodules
fixes: #351

---

opening this so people don't work on it (it's already fixed), but leaving it draft so the other things land first (it includes #340 and the merge will already be complicated because #350, and it's not necessary for 0.7 AIUI).


Patch:
diff --git a/ops/lib/__init__.py b/ops/lib/__init__.py
index edb9fca..b69ce0a 100644
--- a/ops/lib/__init__.py
+++ b/ops/lib/__init__.py
@@ -113,7 +113,7 @@ def _find_all_specs(path):
             if finder is None or not hasattr(finder, 'find_spec'):
                 continue
             for lib_dir in lib_dirs:
-                spec = finder.find_spec(lib_dir)
+                spec = finder.find_spec("{}.opslib.{}".format(top_dir, lib_dir))
                 if spec is None:
                     continue
                 if spec.loader is None:


Test patch:
diff --git a/test/test_lib.py b/test/test_lib.py
index 7e9af85..6aa5b06 100644
--- a/test/test_lib.py
+++ b/test/test_lib.py
@@ -520,6 +520,37 @@ class TestLibFunctional(TestCase):
         baz = ops.lib.use('baz', 2, 'alice@example.com')
         self.assertEqual(baz.LIBAPI, 2)
 
+    def _test_submodule(self, *, relative=False):
+        tmpdir = self._mkdtemp()
+        sys.path = [tmpdir]
+
+        path = _mklib(tmpdir, "foo", "bar")
+        path.write_text(dedent("""
+        LIBNAME = "baz"
+        LIBAPI = 2
+        LIBPATCH = 42
+        LIBAUTHOR = "alice@example.com"
+
+        from {} import quux
+        """).format("." if relative else "foo.opslib.bar"))
+        (path.parent / 'quux.py').write_text(dedent("""
+        this = 42
+        """))
+
+        # reset
+        ops.lib.autoimport()
+
+        # sanity check that ops.lib.use works
+        baz = ops.lib.use('baz', 2, 'alice@example.com')
+        self.assertEqual(baz.LIBAPI, 2)
+        self.assertEqual(baz.quux.this, 42)
+
+    def test_submodule_absolute(self):
+        self._test_submodule(relative=False)
+
+    def test_submodule_relative(self):
+        self._test_submodule(relative=True)
+
     def test_others_found(self):
         tmpdir = self._mkdtemp()
         sys.path = [tmpdir]
Repository: canonical/operator
Pull request #379 (2020-08-14 18:21:53)

Assume version 0.0.0 if JUJU_VERSION is not set
fixes: #365 

dstathis: > I'd like to avoid parsing '0.0.0', but it's probably not worth it.

This would involve instantiating an instance without __init__ running so I think you are right that it is not worth it.

Patch:
diff --git a/ops/jujuversion.py b/ops/jujuversion.py
index 1a8ed66..3401912 100755
--- a/ops/jujuversion.py
+++ b/ops/jujuversion.py
@@ -89,8 +89,8 @@ class JujuVersion:
     def from_environ(cls) -> 'JujuVersion':
         """Build a JujuVersion from JUJU_VERSION."""
         v = os.environ.get('JUJU_VERSION')
-        if not v:
-            raise RuntimeError('environ has no JUJU_VERSION')
+        if v is None:
+            v = '0.0.0'
         return cls(v)
 
     def has_app_data(self) -> bool:


Test patch:
diff --git a/test/test_jujuversion.py b/test/test_jujuversion.py
index cb3a60d..021aa49 100755
--- a/test/test_jujuversion.py
+++ b/test/test_jujuversion.py
@@ -44,8 +44,9 @@ class TestJujuVersion(unittest.TestCase):
 
     @unittest.mock.patch('os.environ', new={})
     def test_from_environ(self):
-        with self.assertRaisesRegex(RuntimeError, 'environ has no JUJU_VERSION'):
-            JujuVersion.from_environ()
+        # JUJU_VERSION is not set
+        v = JujuVersion.from_environ()
+        self.assertEqual(v, JujuVersion('0.0.0'))
 
         os.environ['JUJU_VERSION'] = 'no'
         with self.assertRaisesRegex(RuntimeError, 'not a valid Juju version'):
Repository: canonical/operator
Pull request #399 (2020-09-08 11:50:58)

Handle removal of non-existant relation data
'relation-set foo=' should not fail, even if the key does not
exist in the relation data.


Patch:
diff --git a/ops/model.py b/ops/model.py
index 2cad5dc..840e156 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -694,7 +694,7 @@ class RelationDataContent(LazyMapping, MutableMapping):
             if value == '':
                 # Match the behavior of Juju, which is that setting the value to an
                 # empty string will remove the key entirely from the relation data.
-                del self._data[key]
+                self._data.pop(key, None)
             else:
                 self._data[key] = value
 


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index 2535b1f..75ce02b 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -301,6 +301,26 @@ class TestModel(unittest.TestCase):
             ('relation_set', relation_id, 'host', '', False),
         ])
 
+    def test_relation_data_del_missing_key(self):
+        relation_id = self.harness.add_relation('db1', 'remoteapp1')
+        self.harness.update_relation_data(relation_id, 'myapp/0', {'host': 'bar'})
+        self.harness.add_relation_unit(relation_id, 'remoteapp1/0')
+        self.resetBackendCalls()
+
+        rel_db1 = self.model.get_relation('db1')
+        # Force memory cache to be loaded.
+        self.assertIn('host', rel_db1.data[self.model.unit])
+        rel_db1.data[self.model.unit]['port'] = ''   # Same as a delete, should not fail.
+        self.assertNotIn('port', rel_db1.data[self.model.unit])
+        self.assertEqual({'host': 'bar'}, self.harness.get_relation_data(relation_id, 'myapp/0'))
+
+        self.assertBackendCalls([
+            ('relation_ids', 'db1'),
+            ('relation_list', relation_id),
+            ('relation_get', relation_id, 'myapp/0', False),
+            ('relation_set', relation_id, 'port', '', False),
+        ])
+
     def test_relation_set_fail(self):
         relation_id = self.harness.add_relation('db1', 'remoteapp1')
         self.harness.update_relation_data(relation_id, 'myapp/0', {'host': 'myapp-0'})
Repository: canonical/operator
Pull request #419 (2020-09-30 21:12:23)

Add config alias to CharmBase
As [discussed on Discourse][comment], using `self.model.config` can cause the charm author to believe they are accessing the model-level config. It's also extra typing for something that is going to be frequently accessed, so like the other frequently accessed attributes like `self.app`, `self.unit`, etc, a `self.config` alias seems easier and less likely to lead to confusion.

[comment]: https://discourse.juju.is/t/first-steps-with-the-operator-framework/3006/12


Patch:
diff --git a/ops/charm.py b/ops/charm.py
index d898de8..aab2dd7 100755
--- a/ops/charm.py
+++ b/ops/charm.py
@@ -388,6 +388,11 @@ class CharmBase(Object):
         """
         return self.framework.charm_dir
 
+    @property
+    def config(self) -> model.ConfigData:
+        """A mapping containing the charm's config and current values."""
+        return self.model.config
+
 
 class CharmMeta:
     """Object containing the metadata for the charm.


Test patch:
diff --git a/test/test_charm.py b/test/test_charm.py
index e6ed0b2..dd2cc0a 100755
--- a/test/test_charm.py
+++ b/test/test_charm.py
@@ -105,6 +105,7 @@ class TestCharm(unittest.TestCase):
         self.assertEqual(charm.unit, framework.model.unit)
         self.assertEqual(charm.meta, framework.meta)
         self.assertEqual(charm.charm_dir, framework.charm_dir)
+        self.assertIs(charm.config, framework.model.config)
 
     def test_relation_events(self):
Repository: canonical/operator
Pull request #443 (2020-11-05 16:46:40)

Don't skip interfaces without names: they don't have names on k8s
…rnetes


Patch:
diff --git a/ops/model.py b/ops/model.py
index 86fd6e5..94cd523 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -542,8 +542,6 @@ class Network:
         # interfaces with the same name.
         for interface_info in network_info.get('bind-addresses', []):
             interface_name = interface_info.get('interface-name')
-            if not interface_name:
-                continue
             for address_info in interface_info.get('addresses', []):
                 self.interfaces.append(NetworkInterface(interface_name, address_info))
         self.ingress_addresses = []


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index 6f8f7fe..e8a81ab 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -874,6 +874,40 @@ class TestModelBindings(unittest.TestCase):
         self._check_binding_data(binding_name, binding)
         self.assertEqual(fake_script_calls(self, clear=True), expected_calls)
 
+    def test_binding_no_iface_name(self):
+        network_get_out_obj = {
+            'bind-addresses': [
+                {
+                    'mac-address': '',
+                    'interface-name': '',
+                    'addresses': [
+                        {
+                            'hostname':  '',
+                            'value': '10.1.89.35',
+                            'cidr': ''
+                        }
+                    ]
+                }
+            ],
+            'egress-subnets': [
+                '10.152.183.158/32'
+            ],
+            'ingress-addresses': [
+                '10.152.183.158'
+            ]
+        }
+        network_get_out = json.dumps(network_get_out_obj)
+        fake_script(self, 'network-get',
+                    '''[ "$1" = db0 ] && echo '{}' || exit 1'''.format(network_get_out))
+        binding_name = 'db0'
+        expected_calls = [['network-get', 'db0', '--format=json']]
+
+        binding = self.model.get_binding(binding_name)
+        self.assertEqual(binding.name, 'db0')
+        self.assertEqual(binding.network.bind_address, ipaddress.ip_address('10.1.89.35'))
+        self.assertEqual(binding.network.ingress_address, ipaddress.ip_address('10.152.183.158'))
+        self.assertEqual(fake_script_calls(self, clear=True), expected_calls)
+
     def test_missing_bind_addresses(self):
         network_data = json.dumps({})
         fake_script(self, 'network-get',
Repository: canonical/operator
Pull request #473 (2021-02-09 19:10:19)

Move "build container socket path" logic to Container where it belongs
This logic is specific to containers, so it shouldn't be in `_ModelBackend.get_pebble()`, but on `Container`. Pebble itself doesn't require containers to work.

This was from Gustavo's [comment on PR 472](https://github.com/canonical/operator/pull/472#discussion_r572791023).


Patch:
diff --git a/ops/model.py b/ops/model.py
index a185e64..4e95eba 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -1016,7 +1016,8 @@ class Container:
         self.name = name
 
         if pebble_client is None:
-            pebble_client = backend.get_pebble(name)
+            socket_path = '/charm/containers/{}/pebble/.pebble.socket'.format(name)
+            pebble_client = backend.get_pebble(socket_path)
         self._pebble = pebble_client
 
     @property
@@ -1359,9 +1360,8 @@ class _ModelBackend:
         cmd.extend(metric_args)
         self._run(*cmd)
 
-    def get_pebble(self, container_name: str) -> 'pebble.Client':
-        """Create a pebble.Client instance for given container name."""
-        socket_path = '/charm/containers/{}/pebble/.pebble.socket'.format(container_name)
+    def get_pebble(self, socket_path: str) -> 'pebble.Client':
+        """Create a pebble.Client instance from given socket path."""
         return pebble.Client(socket_path=socket_path)
 
 


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index 2cdc91d..489554c 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -20,7 +20,6 @@ import os
 import pathlib
 from textwrap import dedent
 import unittest
-import unittest.mock
 
 import ops.model
 import ops.charm
@@ -789,9 +788,8 @@ containers:
   c1:
     k: v
 """)
-        backend = ops.model._ModelBackend('myapp/0')
-        with unittest.mock.patch('ops.pebble.Client', MockPebbleClient):
-            self.model = ops.model.Model(meta, backend)
+        backend = MockPebbleBackend('myapp/0')
+        self.model = ops.model.Model(meta, backend)
         self.container = self.model.unit.containers['c1']
         self.pebble = self.container.pebble
 
@@ -836,6 +834,11 @@ containers:
         self.assertEqual(layer.summary, 'foo')
 
 
+class MockPebbleBackend(ops.model._ModelBackend):
+    def get_pebble(self, socket_path):
+        return MockPebbleClient(socket_path)
+
+
 class MockPebbleClient:
     def __init__(self, socket_path):
         self.socket_path = socket_path
Repository: canonical/operator
Pull request #479 (2021-02-15 02:12:50)

Update Pebble container socket path
Per @hpidcock's request as the container socket path has changed.

hpidcock: > The changes are fine, though I thought there was discussion about moving to /var/lib/something
> If this is the current plan, then obviously we should be following suit.

/var/lib/pebble is pebble default directory. So those discussions about /var/lib were to do with pebble's defaults.

/charm path is driven by juju through the PEBBLE_SOCKET env var inside the workload container.

Patch:
diff --git a/ops/model.py b/ops/model.py
index 4ad7483..9efec7d 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -1016,7 +1016,7 @@ class Container:
         self.name = name
 
         if pebble_client is None:
-            socket_path = '/charm/containers/{}/pebble/.pebble.socket'.format(name)
+            socket_path = '/charm/containers/{}/pebble.socket'.format(name)
             pebble_client = backend.get_pebble(socket_path)
         self._pebble = pebble_client
 


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index 96c8843..6c2bde5 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -794,7 +794,7 @@ containers:
         self.pebble = self.container.pebble
 
     def test_socket_path(self):
-        self.assertEqual(self.pebble.socket_path, '/charm/containers/c1/pebble/.pebble.socket')
+        self.assertEqual(self.pebble.socket_path, '/charm/containers/c1/pebble.socket')
 
     def test_autostart(self):
         self.container.autostart()
Repository: canonical/operator
Pull request #496 (2021-03-28 21:16:57)

Make add_layer()'s "combine" a keyword-only parameter
Per https://github.com/canonical/operator/issues/495, a bare `True` at the end of an `add_layer()` call does not read well. Make `combine` a keyword-only parameter, so you have to say `combine=True`.


Patch:
diff --git a/ops/model.py b/ops/model.py
index 4c57663..ae9af50 100644
--- a/ops/model.py
+++ b/ops/model.py
@@ -1047,7 +1047,7 @@ class Container:
         """Stop given service(s) by name."""
         self._pebble.stop_services(service_names)
 
-    def add_layer(self, label: str, layer: typing.Union[str, typing.Dict, 'pebble.Layer'],
+    def add_layer(self, label: str, layer: typing.Union[str, typing.Dict, 'pebble.Layer'], *,
                   combine: bool = False):
         """Dynamically add a new layer onto the Pebble configuration layers.
 
diff --git a/ops/pebble.py b/ops/pebble.py
index 38b894f..9ecfe98 100644
--- a/ops/pebble.py
+++ b/ops/pebble.py
@@ -720,7 +720,7 @@ class Client:
         raise TimeoutError(
             'timed out waiting for change {} ({} seconds)'.format(change_id, timeout))
 
-    def add_layer(self, label: str, layer: Union[str, dict, Layer], combine: bool = False):
+    def add_layer(self, label: str, layer: Union[str, dict, Layer], *, combine: bool = False):
         """Dynamically add a new layer onto the Pebble configuration layers.
 
         If combine is False (the default), append the new layer as the top


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index c84c649..80b5137 100755
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -860,6 +860,10 @@ containers:
             ('add_layer', 'd', 'summary: str\n', True),
         ])
 
+        # combine is a keyword-only arg (should be combine=True)
+        with self.assertRaises(TypeError):
+            self.container.add_layer('x', {}, True)
+
     def test_get_plan(self):
         plan_yaml = 'services:\n foo:\n  override: replace\n  command: bar'
         self.pebble.responses.append(ops.pebble.Plan(plan_yaml))
diff --git a/test/test_pebble.py b/test/test_pebble.py
index bfc2c69..4d6dddb 100644
--- a/test/test_pebble.py
+++ b/test/test_pebble.py
@@ -961,6 +961,10 @@ services:
         with self.assertRaises(TypeError):
             self.client.add_layer(42, 'foo')
 
+        # combine is a keyword-only arg (should be combine=True)
+        with self.assertRaises(TypeError):
+            self.client.add_layer('foo', {}, True)
+
     def test_get_plan(self):
         plan_yaml = """
 services:
Repository: ioos/compliance-checker
Pull request #1017 (2023-04-30 02:21:09)

Check strict monotonicity of coordinate variables


Patch:
diff --git a/compliance_checker/cf/cf_1_6.py b/compliance_checker/cf/cf_1_6.py
index 5a6b4c4..7bb2a44 100644
--- a/compliance_checker/cf/cf_1_6.py
+++ b/compliance_checker/cf/cf_1_6.py
@@ -564,6 +564,26 @@ class CF1_6Check(CFNCCheck):
             )
         return valid_globals.to_result()
 
+    # IMPLEMENTATION
+    def check_coordinate_variables_strict_monotonicity(self, ds):
+        """
+        Checks that data in coordinate variables is either monotonically
+        increasing or decreasing
+        """
+
+        ret_val = []
+        for coord_var_name in self._find_coord_vars(ds):
+            coord_var = ds.variables[coord_var_name]
+            arr_diff = np.diff(coord_var)
+            monotonicity = TestCtx(BaseCheck.HIGH, self.section_titles["5"])
+            monotonicity.assert_true(
+                np.all(arr_diff > 0) or np.all(arr_diff < 0),
+                f'Coordinate variable "{coord_var_name}" must be strictly monotonic',
+            )
+            ret_val.append(monotonicity.to_result())
+
+        return ret_val
+
     def check_convention_possibly_var_attrs(self, ds):
         """
         Check variable and global attributes are strings for recommended attributes under CF §2.6.2


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index a29b7c4..bb5562c 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -122,6 +122,12 @@ class TestCF1_6(BaseTestCase):
         # present in coord_data_vars
         self.assertEqual(self.cf.coord_data_vars, {"time", "sigma"})
 
+        ds = MockTimeSeries()
+        ds.variables["time"][:3] = np.array([20, -2, 0])
+        result = self.cf.check_coordinate_variables_strict_monotonicity(ds)
+        _, _, messages = get_results(result)
+        assert 'Coordinate variable "time" must be strictly monotonic' in messages
+
     # --------------------------------------------------------------------------------
     # Compliance Tests
     # --------------------------------------------------------------------------------
Repository: ioos/compliance-checker
Pull request #1157 (2025-04-08 19:49:35)

Reject negative year units in Julian or Gregorian calendar time units


Patch:
diff --git a/compliance_checker/cf/cf_1_9.py b/compliance_checker/cf/cf_1_9.py
index f63cf0d..3911c78 100644
--- a/compliance_checker/cf/cf_1_9.py
+++ b/compliance_checker/cf/cf_1_9.py
@@ -71,8 +71,22 @@ class CF1_9Check(CF1_8Check):
                         self.section_titles["4.4.1"],
                         [reasoning],
                     )
+                elif reference_year < 0:
+                    reasoning = (
+                        f'For time variable "{time_var.name}", when using '
+                        "the Gregorian or Julian calendars, negative "
+                        "years are not allowed."
+                    )
+                    result = Result(
+                        BaseCheck.MEDIUM,
+                        False,
+                        self.section_titles["4.4.1"],
+                        [reasoning],
+                    )
+                else:
+                    return ret_val
 
-                    ret_val.append(result)
+                ret_val.append(result)
         return ret_val
 
     def check_time_coordinate(self, ds):


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index 187d7a7..0dbbef5 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -3210,6 +3210,18 @@ class TestCF1_9(BaseTestCase):
                 "variable as in COARDS is deprecated in CF." in messages
             )
 
+        # test less than year zero for Julian and Gregorian, which should fail
+        dataset = MockTimeSeries()
+        dataset.variables["time"].units = "seconds since -1-12-31 23:59:59"
+        for calendar_name in ("standard", "julian", "gregorian"):
+            dataset.variables["time"].calendar = calendar_name
+            results = self.cf.check_time_coordinate_variable_has_calendar(dataset)
+            scored, out_of, messages = get_results(results)
+            assert (
+                'For time variable "time", when using the Gregorian or Julian '
+                "calendars, negative years are not allowed." in messages
+            )
+
     def test_domain(self):
         dataset = MockTimeSeries()
         domain_var = dataset.createVariable("domain", "c", ())